Since I replaced Fully Kiosk with Kiosk Satellite on my wall tablets, I’ve been very happy with it. There was just one thing left that annoyed me: updates. Kiosk Satellite downloads a new version by itself, but then Android shows the familiar Install dialog, and somebody has to walk to the tablet and tap it. With several tablets around the house, that somebody was always me.
The usual answers are ADB, Shizuku or the Home Assistant Companion app. I tried going down that road, but I wanted something simpler that keeps working after a reboot, without a laptop and without cables. So I built a small Android app of my own: InstallWatchdog. It sits quietly in the background, waits for the Install dialog to appear, and taps the button for me. And because everything in my house ends up in Home Assistant, it talks to HA as well.
In this post I’ll walk through what the app does, how the shared secrets and the HA webhook work, how I build and deploy it with Android Studio, and what the actual auto-update looks like.
What InstallWatchdog does
- Watches the Android package installer and taps Install / Update as soon as the dialog appears
- Only for apps on an allowlist (by default: Kiosk Satellite), so it never installs random apps
- Re-opens Kiosk Satellite after the update, because the update closes it
- Small HTTP API on each tablet, protected with a token, so Home Assistant can trigger a click or push an APK
- Sends events to a Home Assistant webhook: clicked, failed, app updated, heartbeat and more
- Survives reboots and alerts HA if it ever gets switched off
- No ADB, no Shizuku, no root and no HA Companion app needed
- Runs on Android 7.0 and newer, including LineageOS (tested on Lenovo ThinkSmart View, Lenovo Tab P11 and Samsung Galaxy Tab S2)
How it works
The heart of the app is an Android accessibility service. Accessibility services can read what is on screen and press buttons, which is exactly what a person does when they tap Install. The big advantage: once you switch it on in the Accessibility settings, Android starts it again by itself after every reboot. No foreground service, no boot hacks.
When the installer dialog pops up, InstallWatchdog:
- Checks that the dialog belongs to the system installer (
com.android.packageinstalleron LineageOS,com.google.android.packageinstalleron Google builds) - Reads the text in the dialog and checks it against the allowlist
- Looks up the Install or Update button, by its view ID first and by its label second (English, Dutch and German are built in)
- Taps it. Android keeps the button disabled for a split second as tap-jacking protection, so the app retries a few times until the button accepts the tap
- Closes the “Done” screen if there is one, and re-opens Kiosk Satellite a few seconds after the update
Next to that, the same service runs a tiny HTTP server on port 8765. Home Assistant uses it to send commands, and the app reports back through a webhook. More on both below.
What you need
- One or more Android tablets with Android 7.0 or newer
- Android Studio on your PC (free)
- Home Assistant on the same network
- A fixed IP address for each tablet (a DHCP reservation on your router)
- No PIN or pattern lock on the tablets: the installer dialog can’t appear over a secure lock screen. “None” or “Swipe” is fine
Step 1: The project
InstallWatchdog is a plain Android Studio project without any third-party libraries. After unzipping, it looks like this:
InstallWatchdog/
watchdog.properties shared settings (token, webhook, allowlist)
settings.gradle.kts
build.gradle.kts
gradle.properties
app/
build.gradle.kts
src/main/AndroidManifest.xml
src/main/java/com/rutg3r/installwatchdog/
WatchdogService.kt the accessibility service that taps Install
Api.kt the HTTP API for Home Assistant
HttpServer.kt tiny HTTP server, no dependencies
Webhook.kt sends events to Home Assistant
Prefs.kt settings and baked-in defaults
MainActivity.kt the setup screen on the tablet
BootReceiver.kt checks the service after a reboot
InstallResultReceiver.kt
Compat.kt Android 7 support
Download(s) at the bottom of this blog post.
Step 2: The shared settings and secrets
The first version of the app asked for the token and the webhook on every tablet, and each tablet generated its own random token. With more than one tablet that gets messy fast, because Home Assistant then needs a separate secret per tablet. So I moved everything that is the same for all tablets into one file, watchdog.properties, in the root of the project. Its values get baked into the APK at build time:
# The token HA uses (the part after "Bearer " in secrets.yaml)
# Leave empty to let each tablet generate its own random token.
token=YOUR_SHARED_TOKEN
# Home Assistant webhook that receives the events
webhook_url=http://192.168.1.10:8123/api/webhook/install_watchdog
# App names as shown in the installer dialog, comma separated
allowlist=Kiosk Satellite
A few things to keep in mind:
-
- token: any long random string. Use the same value in your Home Assistant
secrets.yaml. No quotes and no “Bearer” in this file
In HA it looks like:
watchdog_auth: “Bearer h74Y3cYZblablaetc”In the watchdog.properties file it looks like (without quotes):
token=h74Y3cYZblablaetc - webhook_url: use the IP address of Home Assistant, not
homeassistant.local. Many Android versions can’t resolve .local names - allowlist: the app name exactly as the installer shows it. Add
Install Watchdogif you also want to update the app itself from HA (see below) - The file is listed in
.gitignore, so your token never ends up in a Git repository
- token: any long random string. Use the same value in your Home Assistant
With this in place, the only thing you set on each tablet is its device name.
Step 3: Build the APK with Android Studio
- Install Android Studio and choose the Standard setup on first launch. It downloads the Android SDK, which takes a while
- Unzip the project to a simple folder, for example
C:DevInstallWatchdog - Fill in
watchdog.propertiesas described above - In Android Studio choose File > Open and select the unzipped InstallWatchdog folder, the one that contains
settings.gradle.kts - Click Trust Project and wait for the Gradle sync to finish. The first sync downloads Gradle and the Android plugin
- Choose Build > Clean Project, then Build > Build App Bundle(s) / APK(s) > Build APK(s)
- Click locate in the popup. The APK is at
app/build/outputs/apk/debug/app-debug.apk
Three mistakes I made along the way, so you don’t have to:
- Don’t open the zip file itself in Android Studio. It happily shows the contents, but you can’t build from it. I ended up building from an old folder and got a random token and an empty webhook
- Don’t use the green Run button to create the APK for the tablets. Those builds are marked as test-only and often refuse to install from a file manager
- Build updates on the same PC. Android Studio signs the APK with a debug key stored on your computer. Android only accepts an update that is signed with the same key
Step 4: Copy the APK to the tablets
No USB cable needed. I use the Kiosk Satellite app to upload the apk file to each device.
For this open the Kiosk Satellite web interface. Go to System – File Manager on the left side. Grant permission in case this is not made. Acknowledge on the Android device.
From here, in the top right corner
- Click on Upload File.
- Locate your apk file and upload it to the device. (I’ve renamed the apk first to “InstallWatchdog.apk” to make it more clear).
Step 5: Set up the app on each tablet
on Android, go to My Files, navigate to the folder you’ve uploaded the apk file to. Open the apk file and work from top to bottom:
- Android 13 and newer only: tap App info, open the menu (three dots) and choose Allow restricted settings. Without this the accessibility switch stays greyed out for sideloaded apps
- Tap 1. Enable accessibility service, select Install Watchdog and switch it on. On Samsung it’s under Installed services
- Tap 2. Allow installing apps. This is only needed if Home Assistant should push APKs. On Android 7 this opens the global Unknown sources switch
- Enter a Device name, for example
thinksmart_kitchen, and tap Save - Tap Send test event to HA
The top of the screen shows whether everything is in place:
Watchdog running
May install apps
API: http://192.168.1.51:8765/status
Version 1.2.0 - built-in token: in use
Built-in webhook: http://192.168.1.10:8123/api/webhook/install_watchdog
If it says the built-in token is NONE/empty, your watchdog.properties wasn’t picked up during the build.
One more setting outside the app: Kiosk Satellite itself needs permission to install unknown apps (Settings > Apps > Kiosk Satellite > Install unknown apps), otherwise it can’t open the installer in the first place.
Step 6: Home Assistant configuration
This step is optional. The webhook mentioned in the InstallWatchdog app is not needed to have the app auto-click on the Install button during Kiosk Satellite app updates. If you want to have more feedback to HA about what is going on, this step might be for you.
The secret
In secrets.yaml, use the same token as in watchdog.properties, this time with “Bearer ” in front of it:
watchdog_auth: "Bearer YOUR_SHARED_TOKEN"
REST commands
In configuration.yaml. Leave {{ host }} and {{ apk_url }} as they are: you fill them in when you call the action, so one set of commands works for all tablets.
rest_command:
watchdog_click:
url: "http://{{ host }}:8765/click"
method: POST
headers:
Authorization: !secret watchdog_auth
watchdog_install:
url: "http://{{ host }}:8765/install"
method: POST
headers:
Authorization: !secret watchdog_auth
content_type: "application/json"
payload: '{"url": "{{ apk_url }}"}'
watchdog_auto_click:
url: "http://{{ host }}:8765/config"
method: POST
headers:
Authorization: !secret watchdog_auth
content_type: "application/json"
payload: '{"auto_click": {{ enabled | lower }}}'
After a restart, test it in Developer Tools > Actions:
action: rest_command.watchdog_click
data:
host: 192.168.1.51
The response tells you right away what’s going on:
- 200 with
dialog_found: false: all good. The tablet is reachable and waits 15 seconds for an Install dialog - 401: the token doesn’t match
- 400 on
watchdog_install: you forgot theapk_url - A timeout: wrong IP, or the accessibility service isn’t running
Click on all tablets at once
script:
watchdog_click_all:
alias: Watchdog - press Install on all tablets
sequence:
- repeat:
for_each:
- 192.168.1.51
- 192.168.1.52
- 192.168.1.53
sequence:
- action: rest_command.watchdog_click
data:
host: "{{ repeat.item }}"
continue_on_error: true
The webhook: events from the tablets
Every tablet reports what it does to the same webhook. The token plays no role here: it only protects the direction from Home Assistant to the tablet. The device name in each message tells the tablets apart.
Home Assistant allows only one listener per webhook ID, so I use a single automation that receives the webhook, writes it to the logbook, creates a notification when something goes wrong, and passes every message on as an install_watchdog event:
- alias: Install Watchdog events
triggers:
- trigger: webhook
webhook_id: install_watchdog
allowed_methods:
- POST
local_only: true
actions:
- event: install_watchdog
event_data:
device: "{{ trigger.json.device }}"
event: "{{ trigger.json.event }}"
ip: "{{ trigger.json.ip }}"
app: "{{ trigger.json.app | default('') }}"
package: "{{ trigger.json.package | default('') }}"
version: "{{ trigger.json.version | default('') }}"
message: "{{ trigger.json.message | default('') }}"
- action: logbook.log
data:
name: "Install Watchdog {{ trigger.json.device }}"
message: "{{ trigger.json.event }} {{ trigger.json.app | default(trigger.json.package | default('')) }}"
- if: >
{{ trigger.json.event in ('install_click_failed', 'accessibility_disabled', 'download_failed')
or (trigger.json.event == 'install_result' and not trigger.json.success) }}
then:
- action: persistent_notification.create
data:
title: "Install Watchdog - {{ trigger.json.device }}"
message: "{{ trigger.json.event }}: {{ trigger.json.message | default('') }}"
A sensor per tablet
With that event in place, every tablet gets its own trigger-based template sensor. The state is the last event, and the attributes hold when the tablet was last seen, its IP and the installed Kiosk Satellite version. Copy the block for each device name; it must match the name you entered in the app exactly:
template:
- trigger:
- trigger: event
event_type: install_watchdog
event_data:
device: thinksmart_kitchen
sensor:
- name: "Watchdog thinksmart_kitchen"
unique_id: watchdog_thinksmart_kitchen
icon: mdi:shield-check
state: "{{ trigger.event.data.event }}"
attributes:
last_seen: "{{ now().isoformat() }}"
ip: "{{ trigger.event.data.ip }}"
last_app: "{{ trigger.event.data.app or trigger.event.data.package }}"
kiosk_version: >
{{ trigger.event.data.version
if trigger.event.data.event == 'package_updated' and trigger.event.data.version
else this.attributes.get('kiosk_version', '') }}
last_message: "{{ trigger.event.data.message }}"
And a binary sensor that turns on when a tablet hasn’t sent a heartbeat for 40 minutes:
template:
- binary_sensor:
- name: "Watchdog thinksmart_kitchen offline"
unique_id: watchdog_thinksmart_kitchen_offline
device_class: problem
state: >
{{ state_attr('sensor.watchdog_thinksmart_kitchen', 'last_seen') is none
or (now() - as_datetime(state_attr('sensor.watchdog_thinksmart_kitchen', 'last_seen'))).total_seconds() > 2400 }}
You can find everything back in the Logbook, in the automation Traces (which show the full JSON a tablet sent), and on these sensors, which fit nicely on a system dashboard. The quickest way to fill a new sensor is the Send test event button in the app.
Each event contains the event name, the device name, model, IP and Android version, plus details like the app and version. These are the events you can expect:
| Event | When |
|---|---|
service_started |
After a reboot or when the service is switched on |
heartbeat |
Every 15 minutes, so you can alert when a tablet goes quiet |
install_prompt |
Install dialog seen while auto-click is off, waiting for HA |
install_clicked |
Install or Update was tapped |
install_click_failed |
The button stayed disabled or couldn’t be tapped |
install_prompt_ignored |
An install dialog for an app that is not on the allowlist |
package_updated |
An app was updated, with its new version |
app_relaunched |
The updated app was opened again |
download_started / download_failed |
For APKs pushed by Home Assistant |
install_result |
Result of an install pushed by Home Assistant |
accessibility_disabled |
After a reboot or update the service came back switched off |
Step 7: The auto-update in action
This is the part I built it for. When a new Kiosk Satellite version is available, I start the update from my Home Assistant dashboard, just like before. From there everything happens by itself:
- Kiosk Satellite downloads the new version
- Kiosk Satellite opens the Android Install dialog
- InstallWatchdog recognises Kiosk Satellite on the allowlist and taps Update within a second or two
- Android installs the update and closes Kiosk Satellite
- InstallWatchdog opens Kiosk Satellite again
- Home Assistant logs
install_clicked,package_updatedwith the new version, andapp_relaunched
InstallWatchdog never touches the APK file itself. It only presses the button that Kiosk Satellite brings up, so it doesn’t need access to the download.
With auto-click switched on, you don’t need any REST command for this. If you prefer to stay in control, switch auto-click off (in the app, or with rest_command.watchdog_auto_click). The tablet then sends install_prompt and waits until Home Assistant calls watchdog_click.
Surviving reboots
The accessibility service comes back automatically after every reboot. To be on the safe side the app also:
- Checks after boot whether the service is still enabled, and sends
accessibility_disabledif it isn’t - Sends a heartbeat every 15 minutes
- Catches every error inside the service, because Android may disable an accessibility service that keeps crashing
One tip: never use Force stop on the app. On some ROMs that switches the accessibility service off as well.
API reference
Every endpoint except /ping needs the token, as Authorization: Bearer YOUR_SHARED_TOKEN, as an X-Token header or as ?token= in the URL.
| Method | Path | What it does |
|---|---|---|
| GET | /ping |
Liveness check, no token |
| GET | /status |
Settings and the last event |
| POST | /click |
Tap Install now or within 15 seconds. ?force=1 skips the allowlist |
| POST | /install |
Download an APK from {"url": "..."}, install it and confirm the dialog |
| POST | /config |
Change auto_click, allowlist, relaunch, webhook_url, heartbeat_minutes or device_name |
Troubleshooting
- “There was a problem parsing the package”: the tablet runs an Android version older than the app supports. The first version needed Android 8, which ruled out my Samsung Galaxy Tab S2 on Android 7. Version 1.2 supports Android 7.0 and newer
- Random token or empty webhook in the app:
watchdog.propertieswasn’t read. Check it sits next tosettings.gradle.kts, isn’t secretly calledwatchdog.properties.txt, and that you built from the unzipped folder - 401 Unauthorized: the token in the app and in
secrets.yamldon’t match - The dialog isn’t tapped: check the app name on the allowlist matches the dialog exactly, and look for
install_prompt_ignoredin the logbook. If your tablet uses another language, add its word for Install to the app - Accessibility switch greyed out: on Android 13 and newer, allow restricted settings first
A word on security
An app that can tap buttons for you deserves some care. InstallWatchdog only acts inside the system installer, and only for apps on the allowlist. The API only answers requests with the right token, the webhook is set to local_only, and everything stays on my own network. I would not expose port 8765 to the internet.
Wrapping up
What started as a small annoyance turned into a neat little tool. My wall tablets now update Kiosk Satellite without me walking around the house, Home Assistant knows exactly what happened on which tablet, and after a reboot everything just keeps working. Adding a new tablet now takes a couple of minutes: install the APK, switch on the accessibility service and give it a name.
A video about the installation and configuration of the apk file once it’s uploaded.
Facebook: https://www.facebook.com/share/v/1Ez7gpcKTB/
Youtube: https://youtu.be/u6quMebahT8
Attachments
-
InstallWatchdog v1.2
File size: 4 MB Downloads: 1


