DisplayLauncher
Display Launcher is an API-controlled Android launcher for non-interactive displays like kiosks and digital signage. It launches apps programmatically via HTTP endpoints and includes a web-based control panel for remote management, eliminating the need for touch interaction.
Install / Use
/learn @mouldybread/DisplayLauncherREADME
Display Launcher
A headless Android launcher designed for digital signage, kiosks, and remote-controlled displays. Control which apps run on your Android device via a simple web API or browser interface.
Foreword
Android 14 introduces enhanced ADB security which disables and randomises the port used after sleep/reboot, breaking my automation setup. While Auto ADB Enable provides a workaround, Display Launcher helps to eliminate the need for ADB entirely by providing direct REST API control for app launching.
Part of an ADB-free ecosystem:
- Auto ADB Enable - Temporary workaround for legacy workflows
- Display Launcher (this app) - ADB-free app control with intent extras
- Android Stream Viewer - ADB-free camera display
Display Launcher's intent extras support enables integration with apps like Stream Viewer. Launch Stream Viewer directly to a specific camera from Home Assistant—no ADB required. Combined with Stream Viewer's web configuration, you get complete remote control that's more reliable than ADB (no timeouts, no re-authorization) and works within Android's security model.
[!CAUTION] This application has NO built-in authentication or encryption. The web server runs on port 9091 with unrestricted access to anyone who can reach the device on your network.
❌ DO NOT expose this app directly to the internet
❌ DO NOT port forward 9091 to the internet
❌ DO NOT use on untrusted networks (public WiFi, etc.)
❌ DO NOT assume any built-in security exists
⚠️ NEW: This app can install/uninstall APKs remotely - use only on trusted networks!
Table of Contents
- Overview
- Features
- How It Works
- Installation
- Usage
- On-Screen UI Access
- Configuration
- Caveats & Limitations
- Permissions
- Troubleshooting
- Technical Details
- Documentation
- CI/CD
Overview
Display Launcher runs as a minimal, invisible home screen that allows you to remotely switch between applications without user interaction. Perfect for:
- Digital signage displays - Switch content remotely
- Kiosk systems - Control which app is displayed
- Smart home displays - Change dashboards on demand
- Presentation systems - Switch between apps during demos
- Projector control - Manage content from any device on your network
- Camera display systems - Launch apps with specific configurations (e.g., specific camera views)
Features
- ✅ Web-based API for programmatic app launching
- ✅ Browser interface for manual control
- ✅ Upload and install APK files via web interface
- ✅ Uninstall apps remotely from web interface
- ✅ Intent-based app launching with extras support (deep links, YouTube videos, URLs, app-specific parameters)
- ✅ Headless operation - Shows black screen when not needed
- ✅ Persistent background service - Works even when other apps are running
- ✅ Triple-tap gesture to access settings when needed
- ✅ No accessibility services required
How It Works
Display Launcher consists of five main components:
- MainActivity - Minimal UI shown only when needed (triple-tap center screen)
- LauncherService - Foreground service that keeps the web server running
- LauncherWebServer - HTTP server on port 9091 for remote control
- InstallActivity - Transparent activity for APK installation
- UninstallActivity - Transparent activity for app uninstallation
When apps are launched via the API, they come to the foreground automatically. The launcher itself remains invisible in the background.
New: Apps can now be launched with custom intent extras, enabling advanced integrations like:
- Launching camera viewer apps with specific camera selected
- Opening YouTube videos directly
- Passing configuration parameters to apps
Installation
Requirements
- Android 7.0 (API 24) or higher
- Android 14+ recommended for full foreground service support
Setup
- Install the APK on your Android device
- Open the Display Launcher app
- Triple-tap the center of the screen to show settings
- Tap "Set Default" and select Display Launcher as your home app
- Grant any requested permissions
- The web server starts automatically on port 9091
Setting as Default Launcher (ADB Method)
adb shell cmd package set-home-activity com.tpn.displaylauncher/.MainActivity
Usage
Web Interface
Access the web interface from any device on the same network:
http://[device-ip-address]:9091
The web interface provides:
- List of all installed user apps
- One-click launch buttons
- One-click uninstall buttons
- APK file upload and installation
- Search functionality
- Real-time status messages
REST API
For complete API documentation, see the API Reference.
Get list of installed apps
GET http://[device-ip]:9091/api/apps
Response:
[
{
"name": "Chrome",
"packageName": "com.android.chrome"
}
]
Launch an app
POST http://[device-ip]:9091/api/launch
Content-Type: application/json
{
"packageName": "com.android.chrome"
}
Response:
{
"success": true,
"message": "App launched successfully"
}
Launch an app with intent and extras (NEW/UPDATED)
POST http://[device-ip]:9091/api/launch-intent
Content-Type: application/json
{
"packageName": "com.tpn.streamviewer",
"action": "android.intent.action.MAIN",
"data": "",
"extra_string": "camera_name:FRONTDOOR"
}
Or with individual extra parameters:
{
"packageName": "com.tpn.streamviewer",
"action": "android.intent.action.MAIN",
"extra_camera_name": "FRONTDOOR"
}
Response:
{
"success": true,
"message": "App launched successfully with intent"
}
Intent Examples:
| Use Case | Example |
|---------------------|-----------------------------------------------------------------------------------------|
| YouTube video | {"packageName":"com.google.android.youtube","action":"android.intent.action.VIEW","data":"vnd.youtube://VIDEO_ID"} |
| URL | {"packageName":"com.android.chrome","action":"android.intent.action.VIEW","data":"https://example.com"} |
| App with extras | {"packageName":"com.example.app","action":"android.intent.action.MAIN","extra_string":"key:value"} |
| Camera app | {"packageName":"com.tpn.streamviewer","action":"android.intent.action.MAIN","extra_string":"camera_name:FRONT"} |
Extra String Format:
The extra_string parameter accepts comma-separated key:value pairs:
- Single extra:
"extra_string": "camera_name:FRONTDOOR" - Multiple extras:
"extra_string": "camera_name:FRONT,protocol:mse"
Alternatively, use individual extra_* parameters:
"extra_camera_name": "FRONTDOOR""extra_protocol": "mse"
Uninstall an app
POST http://[device-ip]:9091/api/uninstall
Content-Type: application/json
{
"packageName": "com.example.app"
}
Response:
{
"success": true,
"message": "Uninstall dialog opened"
}
Note: The uninstall confirmation dialog appears on the device screen for security.
Upload and install APK
POST http://[device-ip]:9091/api/upload-apk
Content-Type: multipart/form-data
(Form data with 'file' field containing APK)
Response:
{
"success": true,
"message": "Install dialog opened for uploaded APK"
}
Note: The installation dialog appears on the device screen. Uploaded APK files are automatically cleaned up after 10 minutes.
Examples
cURL
# Launch Chrome
curl -X POST http://192.168.1.100:9091/api/launch -H "Content-Type: application/json" -d '{"packageName":"com.android.chrome"}'
# Launch YouTube video
curl -X POST http://192.168.1.100:9091/api/launch-intent -H "Content-Type: application/json" -d '{"packageName":"com.google.android.youtube","action":"android.intent.action.VIEW","data":"vnd.youtube://dQw4w9WgXcQ"}'
# Launch camera app with specific camera
curl -X POST http://192.168.1.100:9091/api/launch-intent -H "Content-Type: application/json" -d '{"packageName":"com.tpn.streamviewer","action":"android.intent.action.MAIN","data":"","extra_string":"camera_name:FRONTDOOR"}'
# Uninstall an app
curl -X POST http://192.168.1.100:9091/api/uninstall -H "Content-Type: application/json" -d '{"packageName":"com.example.app"}'
# Upload APK
curl -X POST http://192.168.1.100:9091/api/upload-apk -F "file=@/path/to/app.apk"
Python
import requests
# Launch an app
response = requests.post(
'http://192.168.1.100:9091/api/launch',
json={'packageName': 'com.android.chrome'}
)
print(response.json())
# Launch with intent and extras
response = requests.post(
'http://192.168.1.100:9091/api/launch-intent',
json={
'packageName': 'com.tpn.streamviewer',
'action': 'android.intent.action.MAIN',
'data': '',
'extra_string': 'camera_name:FRONTDOOR'
}
)
print(response.json())
# Upload APK
with open('app.apk', 'rb') as f:
files = {'file': f}
response = requests.post('http://192.168.1.100:9091/api/upload-apk', files=files)
print(response.json())
JavaScript
// Launch an app
fetch('http://192.168.1.100:9091/api/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JS
Related Skills
node-connect
352.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.5kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
352.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
