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:

  1. Checks that the dialog belongs to the system installer (com.android.packageinstaller on LineageOS, com.google.android.packageinstaller on Google builds)
  2. Reads the text in the dialog and checks it against the allowlist
  3. Looks up the Install or Update button, by its view ID first and by its label second (English, Dutch and German are built in)
  4. 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
  5. 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 Watchdog if 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

With this in place, the only thing you set on each tablet is its device name.

Step 3: Build the APK with Android Studio

  1. Install Android Studio and choose the Standard setup on first launch. It downloads the Android SDK, which takes a while
  2. Unzip the project to a simple folder, for example C:DevInstallWatchdog
  3. Fill in watchdog.properties as described above
  4. In Android Studio choose File > Open and select the unzipped InstallWatchdog folder, the one that contains settings.gradle.kts
  5. Click Trust Project and wait for the Gradle sync to finish. The first sync downloads Gradle and the Android plugin
  6. Choose Build > Clean Project, then Build > Build App Bundle(s) / APK(s) > Build APK(s)
  7. 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

  1. Click on Upload File.
  2. 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:

  1. 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
  2. Tap 1. Enable accessibility service, select Install Watchdog and switch it on. On Samsung it’s under Installed services
  3. 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
  4. Enter a Device name, for example thinksmart_kitchen, and tap Save
  5. 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 the apk_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:

  1. Kiosk Satellite downloads the new version
  2. Kiosk Satellite opens the Android Install dialog
  3. InstallWatchdog recognises Kiosk Satellite on the allowlist and taps Update within a second or two
  4. Android installs the update and closes Kiosk Satellite
  5. InstallWatchdog opens Kiosk Satellite again
  6. Home Assistant logs install_clicked, package_updated with the new version, and app_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_disabled if 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.properties wasn’t read. Check it sits next to settings.gradle.kts, isn’t secretly called watchdog.properties.txt, and that you built from the unzipped folder
  • 401 Unauthorized: the token in the app and in secrets.yaml don’t match
  • The dialog isn’t tapped: check the app name on the allowlist matches the dialog exactly, and look for install_prompt_ignored in 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

Privacy Preference Center