# FreeKiosk ADB Configuration Guide **Headless provisioning and scripted deployments with secure PIN-based control**

Docs HomeInstallationREST APIMQTT

## Table of Contents - [Overview](#overview) - [Security Model](#security-model) - [Quick Start](#quick-start) - [Configuration Parameters](#configuration-parameters) - [Device States](#device-states) - [Common Commands](#common-commands) - [Command Reference](#command-reference) - [Advanced Scenarios](#advanced-scenarios) - [Troubleshooting](#troubleshooting) - [Related Resources](#related-resources) > [!IMPORTANT] > For production-grade app locking, pair ADB provisioning with **Device Owner mode**. ## Overview FreeKiosk supports configuration via Android Debug Bridge (ADB) intent extras, enabling: | Capability | Description | |---|---| | **Automated Device Provisioning** | Configure without UI interaction | | **Mass Deployment** | Scale across multiple devices | | **Dynamic Configuration** | Change settings via scripts | | **CI/CD Integration** | Automated testing workflows | ## Security Model | Device State | Requirements | |--------------|-------------| | **Virgin setup** (no PIN configured) | PIN **must be provided** in the command | | **Already configured** | Existing PIN **required** to modify | This security model ensures: ✅ **First-time setup is scriptable** with PIN protection ✅ **Configured devices cannot be hijacked** via ADB ✅ **Factory reset re-enables** ADB provisioning ## Quick Start ### First-Time Setup (New Device) ```bash # Set Device Owner (one-time, requires no user accounts on the device) adb shell dpm set-device-owner com.freekiosk/.DeviceAdminReceiver # Configure and lock to external app adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "com.example.myapp" \ --es pin "1234" ``` > [!NOTE] > Setting Device Owner is **highly recommended** for external app locking as it allows FreeKiosk to automatically manage all required permissions including "Display over other apps". Without Device Owner, this permission must be granted manually through Settings. ### Configure WebView Kiosk ```bash adb shell am start -n com.freekiosk/.MainActivity \ --es url "https://your-dashboard.com" \ --es pin "1234" ``` > [!NOTE] > WebView mode doesn't require Device Owner as it doesn't need overlay permissions. ## Command Reference ### Basic Syntax ```bash adb shell am start -n com.freekiosk/.MainActivity [OPTIONS] ``` ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `--es pin "XXXX"` | String | **Always required**. PIN for authentication (new or existing) | ### App Lock Parameters | Parameter | Type | Description | |-----------|------|-------------| | `--es lock_package "com.app"` | String | Package name of app to lock device to | | `--ez auto_start true` | Boolean | Auto-launch the locked app after config | ### Multi-App Parameters | Parameter | Type | Description | |-----------|------|-------------| | `--es external_app_mode "multi"` | String | App mode: `single` (default, classic) or `multi` (home screen grid) | | `--es managed_apps '[...]'` | String (JSON) | JSON array of managed apps (see format below) | **Managed Apps JSON Format:** Each app in the array supports these fields: | Field | Type | Default | Description | |-------|------|---------|-------------| | `packageName` | String | **required** | Android package name | | `displayName` | String | auto-resolved | Display name (auto-detected from system if omitted) | | `showOnHomeScreen` | Boolean | `true` | Show in the multi-app home grid | | `launchOnBoot` | Boolean | `false` | Auto-launch on device boot | | `keepAlive` | Boolean | `false` | Monitor and restart if app crashes | | `allowAccessibility` | Boolean | `false` | Whitelist app's accessibility services | > **Note**: Uninstalled packages in the list are silently skipped with a warning in logcat. ### WebView Parameters | Parameter | Type | Description | |-----------|------|-------------| | `--es url "https://..."` | String | URL to display in kiosk WebView | ### Kiosk Mode Options | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--ez kiosk_enabled true` | Boolean | `true` | Enable/disable kiosk mode | | `--ez auto_start true` | Boolean | - | Auto-launch the locked app after config | | `--es auto_launch "true"` | String | - | Auto-launch on boot (alternative to `auto_start`) | | `--es auto_relaunch "true"` | String | - | Auto-relaunch if app crashes/exits | | `--es test_mode "false"` | String | `"true"` | `"false"` = production (sets `back_button_mode` to `immediate`). `"true"` = testing (sets `back_button_mode` to `test`). Shortcut for `back_button_mode` | | `--es back_button_mode "immediate"` | String | `"test"` | Behavior when returning to FreeKiosk via Android back button: `"test"` (stay on FreeKiosk, back button allowed), `"timer"` (countdown then relaunch app), `"immediate"` (instantly relaunch app). If `test_mode` is also set, `back_button_mode` takes priority | | `--es status_bar "true"` | String | - | Show custom status bar | ### Password Options | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--es pin_mode "numeric"` | String | `numeric` | PIN input mode: `numeric` (4-6 digits) or `alphanumeric` (letters, numbers, special chars) | ### REST API Options | Parameter | Type | Description | |-----------|------|-------------| | `--es rest_api_enabled "true"` | String | Enable REST API server | | `--es rest_api_port "8080"` | String | API server port | | `--es rest_api_key "secret"` | String | API authentication key | ### MQTT Options | Parameter | Type | Description | |-----------|------|-------------| | `--es mqtt_enabled "true"` | String | Enable MQTT client | | `--es mqtt_broker_url "host"` | String | MQTT broker hostname or IP | | `--es mqtt_port "1883"` | String | MQTT broker port | | `--es mqtt_username "user"` | String | MQTT authentication username | | `--es mqtt_password "pass"` | String | MQTT authentication password (stored securely in Keychain) | | `--es mqtt_client_id "id"` | String | MQTT client ID (auto-generated if empty) | | `--es mqtt_base_topic "freekiosk"` | String | Base MQTT topic prefix | | `--es mqtt_discovery_prefix "homeassistant"` | String | Home Assistant MQTT discovery prefix | | `--es mqtt_status_interval "30"` | String | Status publish interval in seconds | | `--es mqtt_allow_control "true"` | String | Allow remote control via MQTT | | `--es mqtt_device_name "name"` | String | Friendly device name for HA discovery | ### Screensaver Options | Parameter | Type | Description | |-----------|------|-------------| | `--es screensaver_enabled "true"` | String | Enable screensaver on inactivity | ## Waiting for Configuration Completion The ADB command returns immediately, but FreeKiosk needs time to save settings and restart. **Use broadcast receivers to wait for completion**: ### TypeScript/JavaScript Example ```typescript private async setupKioskModeUsingFreeKiosk(packageName: string): Promise { console.log(`Setting up kiosk mode for ${packageName}...`); // Start listening for EXTERNAL_APP_LAUNCHED broadcast const appLaunchMonitor = this.execAsync( `adb -s ${this.adbTarget} logcat -c && adb -s ${this.adbTarget} logcat -s "FreeKiosk-ADB" | grep -m 1 "EXTERNAL_APP_LAUNCHED: ${packageName}"` ); // Send configuration command with auto_start const kioskCmd = `adb -s ${this.adbTarget} shell am start -n com.freekiosk/.MainActivity --es lock_package "${packageName}" --es pin "${PIN}" --ez auto_start true`; await this.execAsync(kioskCmd); // Wait for external app to be launched and visible (timeout after 30s) console.log("Waiting for app to launch..."); await Promise.race([ appLaunchMonitor, new Promise((_, reject) => setTimeout(() => reject(new Error('App launch timeout after 30s')), 30000) ) ]); console.log(`✅ ${packageName} is now running and ready!`); // Start sending video/content here } ``` ### Bash Script Example ```bash #!/bin/bash PACKAGE="com.example.app" PIN="1234" # Monitor logcat for completion marker in background adb logcat -c adb logcat -s "FreeKiosk-ADB" | grep -m 1 "SETTINGS_LOADED" & LOGCAT_PID=$! # Send configuration adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "$PACKAGE" \ --es pin "$PIN" \ --ez auto_start true # Wait for completion (with 30s timeout) timeout 30 wait $LOGCAT_PID echo "Configuration complete!" ``` ### Broadcast Events FreeKiosk emits these broadcasts during ADB configuration: | Broadcast Action | When | Description | |-----------------|------|-------------| | `com.freekiosk.ADB_CONFIG_SAVED` | Immediately after config saved | Database write complete, restart imminent | | `com.freekiosk.ADB_CONFIG_RESTARTING` | Just before restart | Process kill in 500ms | | `com.freekiosk.SETTINGS_LOADED` | After restart complete | App restarted and settings loaded successfully | | `com.freekiosk.EXTERNAL_APP_LAUNCHED` | After external app launch (auto_start=true) | External app is launched and visible (includes package_name extra) | **For WebView mode**: Wait for `SETTINGS_LOADED` **For External App mode with auto_start**: Wait for `EXTERNAL_APP_LAUNCHED` to know when the app is ready to receive content ### Waiting for External App Launch ```bash #!/bin/bash PACKAGE="com.example.app" PIN="1234" # Monitor for external app launch adb logcat -c adb logcat -s "FreeKiosk-ADB" | grep -m 1 "EXTERNAL_APP_LAUNCHED: $PACKAGE" & LOGCAT_PID=$! # Configure and auto-launch adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "$PACKAGE" \ --es pin "$PIN" \ --ez auto_start true # Wait for app to be visible (with 30s timeout) timeout 30 wait $LOGCAT_PID echo "✅ $PACKAGE is now running and ready!" # Start sending video/content here ``` ## Examples ### 1. Cloud Gaming Kiosk Lock device to a game streaming app with auto-relaunch (production mode): ```bash adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "com.valvesoftware.steamlink" \ --es pin "1234" \ --es auto_relaunch "true" \ --es test_mode "false" \ --ez auto_start true ``` > [!NOTE] > `test_mode "false"` sets `back_button_mode` to `immediate`, meaning the Android back button will instantly relaunch the app. Omit it or set to `"true"` during testing to allow the back button to return to FreeKiosk without auto-relaunch. ### 🏨 2. Hotel Room Tablet Display hotel dashboard with REST API for Home Assistant: ```bash adb shell am start -n com.freekiosk/.MainActivity \ --es url "https://hotel.local/dashboard" \ --es pin "5678" \ --es rest_api_enabled "true" \ --es rest_api_port "8080" \ --es rest_api_key "hotel_secret_key" \ --es screensaver_enabled "true" ``` ### 🍽️ 3. Restaurant Menu Display Simple URL kiosk: ```bash adb shell am start -n com.freekiosk/.MainActivity \ --es url "https://menu.restaurant.com" \ --es pin "0000" \ --es status_bar "false" ``` ### 4. Multi-App Kiosk Configure a home screen grid with multiple apps: ```bash adb shell am start -n com.freekiosk/.MainActivity \ --es external_app_mode "multi" \ --es managed_apps '[{"packageName":"com.spotify.music"},{"packageName":"com.netflix.mediaclient"},{"packageName":"com.youtube","launchOnBoot":true,"keepAlive":true}]' \ --es pin "1234" \ --es test_mode "false" ``` > **Tip**: Display names are auto-resolved from the system. You can override them: > ```bash > --es managed_apps '[{"packageName":"com.app","displayName":"My Custom Name"}]' > ``` ### 5. Full JSON Configuration For complex setups, use a JSON config. Note that shell escaping can be tricky - using individual parameters is often easier: ```bash # Linux/Mac - use single quotes for JSON adb shell am start -n com.freekiosk/.MainActivity \ --es pin "1234" \ --es config '{"lock_package":"com.app","auto_relaunch":"true"}' # Or use individual parameters (recommended for shell scripts) adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "com.app" \ --es auto_relaunch "true" \ --es status_bar "true" \ --es rest_api_enabled "true" \ --es rest_api_port "8080" \ --es pin "1234" ``` ### 6. Modify Existing Configuration Change the locked app on an already-configured device: ```bash # Must use the existing PIN adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "com.newapp.package" \ --es pin "1234" ``` ## JSON Configuration Format When using `--es config '{...}'`, the following keys are supported: ```json { "url": "https://example.com", "lock_package": "com.example.app", "display_mode": "external_app", "external_app_mode": "multi", "managed_apps": [ {"packageName": "com.app1", "showOnHomeScreen": true}, {"packageName": "com.app2", "launchOnBoot": true, "keepAlive": true} ], "kiosk_enabled": "true", "auto_launch": "true", "auto_relaunch": "true", "screensaver_enabled": "true", "screensaver_delay": "300000", "screensaver_brightness": "10", "status_bar_enabled": "true", "status_bar_show_battery": "true", "status_bar_show_wifi": "true", "status_bar_show_time": "true", "rest_api_enabled": "true", "rest_api_port": "8080", "rest_api_key": "your_api_key", "allow_power_button": "false", "back_button_mode": "immediate", "test_mode": "false", "default_brightness": "75", "brightness_management_enabled": "true", "mqtt_enabled": "true", "mqtt_broker_url": "broker.local", "mqtt_port": "1883", "mqtt_username": "user", "mqtt_password": "password", "mqtt_client_id": "tablet1", "mqtt_base_topic": "freekiosk", "mqtt_discovery_prefix": "homeassistant", "mqtt_status_interval": "30", "mqtt_allow_control": "true", "mqtt_device_name": "Kitchen Tablet" } ``` ## Complete Provisioning Script Here's a complete bash script for provisioning a new device: ```bash #!/bin/bash # provision_kiosk.sh - Provision a FreeKiosk device PACKAGE="com.cloudgaming.app" PIN="1234" API_KEY="my_secret_key" echo "Setting Device Owner..." adb shell dpm set-device-owner com.freekiosk/.DeviceAdminReceiver echo "Waiting for device..." sleep 2 echo "Granting Usage Stats permission (required for foreground monitoring)..." adb shell appops set com.freekiosk android:get_usage_stats allow echo "Granting WRITE_SECURE_SETTINGS (required for auto-enabling accessibility service)..." adb shell pm grant com.freekiosk android.permission.WRITE_SECURE_SETTINGS echo "Configuring FreeKiosk..." adb shell am start -n com.freekiosk/.MainActivity \ --es lock_package "$PACKAGE" \ --es pin "$PIN" \ --es auto_relaunch "true" \ --es test_mode "false" \ --es rest_api_enabled "true" \ --es rest_api_port "8080" \ --es rest_api_key "$API_KEY" \ --ez auto_start true echo "✅ Device provisioned!" echo " Locked to: $PACKAGE" echo " REST API: http://:8080" ``` ## 💻 PowerShell Script (Windows) ```powershell # provision_kiosk.ps1 - Provision a FreeKiosk device (Windows) $Package = "com.cloudgaming.app" $Pin = "1234" $ApiKey = "my_secret_key" Write-Host "Setting Device Owner..." adb shell dpm set-device-owner com.freekiosk/.DeviceAdminReceiver Start-Sleep -Seconds 2 Write-Host "Granting Usage Stats permission..." adb shell appops set com.freekiosk android:get_usage_stats allow Write-Host "Granting WRITE_SECURE_SETTINGS..." adb shell pm grant com.freekiosk android.permission.WRITE_SECURE_SETTINGS Write-Host "Configuring FreeKiosk..." # Note: JSON escaping in PowerShell is complex, use individual parameters adb shell am start -n com.freekiosk/.MainActivity ` --es lock_package $Package ` --es auto_relaunch "true" ` --es test_mode "false" ` --es rest_api_enabled "true" ` --es rest_api_port "8080" ` --es rest_api_key $ApiKey ` --es pin $Pin ` --ez auto_start true Write-Host "✅ Device provisioned!" ``` ## Troubleshooting ### Error: "PIN required for first setup" **Cause**: Device has no PIN configured, but none was provided. **Solution**: Add `--es pin "XXXX"` to your command. ### Error: "PIN required" / "Invalid PIN" **Cause**: Device already configured, wrong or missing PIN. **Solution**: Use the existing PIN configured on the device. ### Error: "Package not found" **Cause**: The `lock_package` app is not installed on the device. **Solution**: Install the target app first: ```bash adb install myapp.apk ``` ### External app doesn't stay on top / disappears **Cause**: FreeKiosk requires "Display over other apps" (SYSTEM_ALERT_WINDOW) permission to keep external apps locked. **Solution**: **Option 1: Set Device Owner (recommended for kiosks)** ```bash adb shell dpm set-device-owner com.freekiosk/.DeviceAdminReceiver ``` With Device Owner, FreeKiosk can automatically grant itself all required permissions including "appear on top" and "Usage Access". **Option 2: Manual permission (without Device Owner)** 1. Go to Settings → Apps → FreeKiosk → Display over other apps 2. Enable the permission manually ### EXTERNAL_APP_LAUNCHED not appearing in logcat **Cause**: Without Device Owner, the "Usage Access" (PACKAGE_USAGE_STATS) permission must be granted manually for FreeKiosk to verify that the external app is in the foreground. **Solution**: Grant the permission via ADB: ```bash adb shell appops set com.freekiosk android:get_usage_stats allow ``` Without this permission, the broadcast will still be emitted but with `(NOT verified)` instead of `(verified in foreground)`. The logcat tag is `FreeKiosk-ADB`: ```bash adb logcat -s "FreeKiosk-ADB" ``` > [!NOTE] > Device Owner can only be set when **no user accounts** are active on the device (Settings → Accounts). Remove all accounts first, then run the command. You can sign back in afterwards. If account removal alone doesn't work, a factory reset is a reliable fallback. Once set, all permissions are managed automatically. ### ♿ Accessibility Service: "Permission denial: WRITE_SECURE_SETTINGS" **Cause**: The `WRITE_SECURE_SETTINGS` permission is required to programmatically enable the Accessibility Service. Being a Device Owner alone does **not** grant this permission automatically. **Solution**: Grant the permission via ADB (one-time): ```bash adb shell pm grant com.freekiosk android.permission.WRITE_SECURE_SETTINGS ``` After granting, tap "Enable Automatically (Device Owner)" in Settings → Advanced → Accessibility Service. The permission persists across reboots. Alternatively, you can enable the Accessibility Service manually: Settings → Accessibility → Installed Services → FreeKiosk. ### ❓ Nothing happens **Cause**: ADB debugging might be disabled or device not authorized. **Solution**: 1. Enable USB Debugging in Developer Options 2. Accept the "Allow USB debugging" prompt on device 3. Verify with `adb devices` ### 🔄 How to reset and re-provision ```bash # Option 1: Factory reset (loses all data) adb shell am broadcast -a android.intent.action.MASTER_CLEAR # Option 2: Remove Device Owner + clear FreeKiosk data adb shell dpm remove-active-admin com.freekiosk/.DeviceAdminReceiver adb shell pm clear com.freekiosk ``` ## Security Considerations | Risk | Mitigation | |---|---| | **ADB Access = Full Control** | Anyone with ADB access to an unlocked device can potentially reconfigure it. Disable USB debugging in production. | | **PIN Storage** | The ADB PIN is saved to Android Keystore via `react-native-keychain` with PBKDF2 hashing (same secure storage as the UI PIN). The native side also keeps a SHA-256 hash for ADB re-authentication. | | **Network ADB** | If using `adb tcpip`, ensure proper network security as anyone on the network could potentially access ADB. | | **Configuration Storage** | ADB configuration is first saved to SharedPreferences as a "pending config", then applied to AsyncStorage by React Native on the next startup. This two-step bridge ensures reliable persistence across process restarts (~500ms). | ### Recommendations: - Use strong PINs (6+ digits) - Disable USB debugging after setup in production - Use Device Owner mode for full kiosk lockdown - Consider physical security of USB port ## See Also - [REST API Documentation](REST-API) - Remote control via HTTP - [MDM Specification](MDM-SPEC) - Enterprise deployment - [Installation Guide](Installation) - Manual setup instructions