SkillAgentSearch skills...

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/DisplayLauncher

README

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:

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

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:

  1. MainActivity - Minimal UI shown only when needed (triple-tap center screen)
  2. LauncherService - Foreground service that keeps the web server running
  3. LauncherWebServer - HTTP server on port 9091 for remote control
  4. InstallActivity - Transparent activity for APK installation
  5. 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

  1. Install the APK on your Android device
  2. Open the Display Launcher app
  3. Triple-tap the center of the screen to show settings
  4. Tap "Set Default" and select Display Launcher as your home app
  5. Grant any requested permissions
  6. 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

View on GitHub
GitHub Stars6
CategoryDevelopment
Updated28d ago
Forks0

Languages

Kotlin

Security Score

90/100

Audited on Mar 11, 2026

No findings