All notable changes to this fork (mhoogenbosch/PiPup) are documented here. The format is loosely based on Keep a Changelog. Original app by rogro82.
Every version below has a GitHub release with the full story (English and Dutch) and the APK.
Requested (#19): show an icon next to the popup's title/message, notification-style.
- Optional
iconfield (an image URL, loaded like other media) shown beside the title/message block, withiconPositionleft(default) orrightandiconWidth(pixels, default 96). Available on both the JSON and multipart/notifyendpoints. The text now sits in a column next to the icon; the media image stays below, and a media-only popup drops the empty header row.
Reported (#18): on a popup with multiple buttons, D-pad navigation worked — the correct button fired — but nothing on screen showed which button was focused, so it looked unresponsive.
- In an overlay window the platform button background carries no focus state, so moving focus with the remote was invisible. Each button now has an explicit focused/unfocused background (a bright fill when focused, a subtle translucent one otherwise) plus a small scale bump on focus, and the first button takes focus when the popup appears — so there is a highlight from the start.
Field report: pressing Install in Home Assistant for a sleeping Fire TV (Android 9) did nothing visible, and afterwards it only said an update was already running.
- Android < 12 cannot install without an on-screen confirmation (the install permission does not change that). The app already turns that into a popup with a button, but it did not wake the screen — so on a sleeping TV the confirmation sat on a black screen and the update stalled invisibly. The app now wakes the screen when a confirmation is pending, so one remote press finishes it.
- The stalled state (
installing: true) used to linger for 15 minutes after an unconfirmed attempt. The pending install is now released as soon as its confirmation popup goes away unconfirmed, so the update can be retried immediately. /state.updategainedpendingUserAction(waiting for the on-screen confirmation) andsilent(false on Android < 12, where an install cannot complete without a remote press), so a controller can say "confirm on the TV" instead of a bare "installing".
Field report: after a mains power cut and restore, if the TV boots to standby without being turned on and is later woken over ADB, PiPup never starts — the connectivity sensor stays offline until the app is opened by hand from the app drawer.
- Root cause:
BOOT_COMPLETED— which the app already listens for to restart itself — is only broadcast once the device reaches a fully started, unlocked user session. A TV that boots to standby after a power restore never reaches that point, and waking it over ADB does not re-fire the boot broadcast, so the service stayed down. - The boot receiver and the service are now
directBootAwareand also listen forLOCKED_BOOT_COMPLETED(plusQUICKBOOT_POWERONfor OEM fast-boot). These fire in the early locked-boot phase, before turn-on, so the service starts on a silent power-restore boot too. - App preferences (the stable device id and version markers — none of them sensitive) moved to device-protected storage so they are readable during direct boot; the existing file is migrated once, so the device id — and therefore the Home Assistant unique_id — stays the same.
Field report from a TCL Smart TV Pro (Android 11): the self-update permission stayed missing no matter
what, and the /permissions/diagnose output showed why — opModes.installPackages: "errored".
- When an app-op is in a device-blocked state (
errored/ignored) — some TVs lock "install unknown apps" for sideloaded apps at the system level, like Samsung's Auto Blocker — the permission screen opens but the toggle will not stick. The app no longer offers a Fix button that leads nowhere: it shows the adb command with a "this TV blocks it" note instead (status screen), andPOST /permissions/fixanswers 501 with that reason + command rather than opening a dead screen. A neutraldefaultop (the normal case) still gets the on-screen Fix button.
Field report: pressing Install in Home Assistant made "a screen flash by" on the TV and then nothing. Diagnosed on hardware: on Android < 12 the installer's confirmation dialog, launched blind from a background receiver, flashes and vanishes (TCL, Android 11) — the session then waits forever on a dialog nobody can reach. On Android 12+ the install is silent and over in seconds, so nothing ever acknowledged the button press.
- "Installing PiPup vX…" popup (with countdown bar) when an update starts via
POST /updateor the app's own update popup. - Confirmation popup with a button when the installer demands on-screen confirmation
(Android < 12): pressing OK launches the system dialog from a visible window, so it keeps focus
instead of flashing away. Repeatable —
POST /updatewhile a confirmation is pending shows the popup again instead of answering "already running". - "PiPup updated to vX" popup after the app replaced itself (screen on only; suppressed on the very first run of a version that introduces the marker).
- These flows run in the new version, so they become visible from the next update cycle onward — updating to 0.11.0 still uses the old, silent code.
Field follow-up: a TV whose Install unknown apps screen shows Allowed while the app reports the self-update permission as MISSING. Those two really can disagree, and the boolean could not say why.
/permissions/diagnosenow reports the raw app-op modes (opModes) for the install and overlay grants. The interesting value isdefault— the state every reinstall resets to, for whichcanRequestPackageInstalls()answers false on the devices measured here, whatever a Settings toggle may show. Also new:installCheckError(a swallowed exception in the check used to be indistinguishable from a revoked permission),user(app-ops are per profile — a Settings screen viewed under another profile shows that profile's state) andtargetSdk.
Full audit of the fork (all findings verified against a concrete failure scenario before fixing).
- Request bodies are now capped at 256 KB.
/notifyallocated a buffer of whateverContent-Lengthclaimed — one LAN request with a large header (or an unbounded chunked body) was an out-of-memory crash of the service. Verified on hardware: a 900 MB Content-Length now gets HTTP 400 and the service stays up.
- Popups longer than ~24.8 days vanished instantly: the removal delay was computed as
Int * 1000before widening toLong, so it overflowed negative and removed the popup immediately. - A self-update whose on-TV confirmation was never accepted blocked all future updates until a service restart: the "installing" flag never cleared. It is now a deadline (15 min) — an abandoned attempt can be replaced.
- Every snapshot popup leaked one file descriptor (
BitmapFactory.decodeStreamdoes not close its stream); a corrupt upload now also gets a clean error instead of a null bitmap. - Removed the dead
CONNECTIVITY_CHANGE/WIFI_STATE_CHANGEDmanifest filters: Android has not delivered those broadcasts to manifest receivers since API 24/26, so the suggested start-on-network-change never happened on any supported device.
All three from one field report (HA forum).
- Screen on/off is no longer presented as a problem. A TV without the power grants showed a permanent yellow MISSING with a Fix button — for a feature that is entirely optional — and its owner understandably kept pressing it through disables, enables and reboots. The two power routes (device admin / accessibility) are now ONE calm line: green configured (via …) when either route works, neutral optional, not configured with a short explanation when neither does. Required permissions (overlay, self-update) keep the loud treatment.
- The permission panel starts mid-screen instead of at the bottom edge: compact logo/status header, and the panel gets the whole lower half, so on most devices nothing needs scrolling.
- Bigger text (18sp headline, 15sp explanation, 14sp adb command — was 14/12/11): this is a 10-foot UI and the old sizes were reported as "very difficult to see".
permissions.deviceAdminreportednullon a Fire TV stick (AFTKRT, Android 11) whose admin is registered and whoselockNow()works — becausehasSystemFeature(FEATURE_DEVICE_ADMIN)is false there./statethen contradicted itself:power.sleepMethod: "device_admin"next topermissions.deviceAdmin: null. An active admin is proof and now outranks the flag;nullmeans only "not active, and no sign the platform supports it".- The fix button for device admin no longer hides behind that flag either. Measured, the flag is wrong
in both directions (false where admins register, and reported true on Fire OS 9), so the honest
filter is the placeholder check that already refuses Fire OS's
CTSDummyDeviceAdminActivity.
Answer to a field report that the permission fix "does not work". Two causes, both invisible from the outside, plus the endpoint to see them.
GET /permissions/diagnose— per permission the intent action, the activity that resolves it, whether that activity is a vendor placeholder, whether it is fixable and the adb command; plussdk/device,deviceAdminSupported,backgroundLaunchExempt,activityVisibleandlastFix(what the previous attempt did and how it ended). Reachable over HTTP, so a bug report can carry facts instead of a symptom — the people who hit this are holding a remote, not a shell. The Home Assistant integration puts the whole block in its diagnostics download.<queries>declarations for the four settings intents.
- A fix requested while the overlay permission was missing did nothing and reported success. From
Android 10 on, starting an activity from the background needs an exemption; holding
SYSTEM_ALERT_WINDOWis one, a foreground service explicitly is not — so the permission you most want a button for is the one whose absence takes the button away, and the platform drops the launch silently./permissions/fixnow checks up front and answers 501 with areasonnaming the way out: open PiPup on the TV (a visible window is another exemption) and use the button there, or use adb. Verified all three ways on a TCL Google TV. - Working screens were reported as unavailable on some devices. The placeholder check used
resolveActivity(), which on Android 11+ is a query and is filtered by package visibility, whilestartActivity()is not. Without<queries>, any device whoseforceQueryablelist omits Settings hid a perfectly good button. An unresolved intent now counts as worth trying; only a recognised placeholder (CTSDummy…,frameworkpackagestubs…) is refused. A Google TV here lists 184forceQueryablepackages including Settings, which is why this never showed up in testing. - The "is my window visible" check was a boolean set by
MainActivity, so the wake step — which launchesWakeActivityin front of it — made a fix request refuse itself one step before launching. The platform's exemption is per app, so it is now a counter over all activities.
v0.8.1 — 2026-08-17 (don't wake the TV for a fix it cannot show)
POST /permissions/fixwoke the TV before checking whether the requested screen exists on that device, so a request that was going to answer 501 anyway still switched a TV on. Seen minutes after 0.8.0 shipped: a failingpipup.fix_permissionfrom Home Assistant, aimed at a Fire TV where the overlay screen is a CTS placeholder, woke a TV in another room and left it on. The capability check now runs first. Verified on a sleeping Fire TV stick — 501 with the adb command, TV stays asleep.
v0.8.0 — 2026-08-17 (permission screen with fix buttons)
- The status screen on the TV lists every permission with its real state and puts a Fix button next to the missing ones, jumping straight to the system screen where it is granted — operable with the remote, no adb prompt needed.
POST /permissions/fixdoes the same from a controller: no argument for the app's own overview,?what=nextfor the first missing one,?what=overlayfor a specific one. The TV is woken first.
- Placeholder intent handlers are treated as absent. Every Android build must resolve these intents
to pass Google's compatibility suite, so "is there an activity for this?" answers yes even where
nothing happens (Fire OS returns
CTSDummyIntentHandler, Google TVframeworkpackagestubs.Stubs). Where the screen is fake the app shows the adb command instead,/statereports it aspermissions.fixable, and/permissions/fixanswers 501 with the command in its body. - The app still cannot grant anything itself — these are app-ops, shell/system territory. It can only walk someone to the exact spot, which was the missing half.
v0.7.0 — 2026-08-17 (border styling, screen on/off, installers)
- Custom border styling on
/notify:borderColor,borderWidth,cornerRadius. Each overrides its part of theurgencypreset, sourgency: critical+borderWidth: 2is a thin red border andborderWidth: 0removes the preset's frame.cornerRadiusworks on borderless popups too. - Screen on/off —
POST /power?state=on|off|toggle. No second integration (ADB, HDMI-CEC) for "wake the TV, then show something". on works out of the box; off needs a one-time grant per device (device admin, or an accessibility fallback for boxes without the device-admin feature)./statepublishespower.canSleepandpower.sleepMethod;/poweranswers 501 where the device cannot, so a client can hide the control instead of offering a dead one. - Permission reporting in
/state(permissions.overlay,installPackages, vendorautoStart,deviceAdmin,accessibility) plus a warning on the TV's status screen when the overlay permission is missing — until now that failure was invisible: every popup was answered with HTTP 200 and nothing appeared. - Installers
install.sh/install.ps1with--power,--accessibilityand a TCL--wakeflag.
- The multipart path learned
borderColor/borderWidth/cornerRadiusplusurgencyandshowProgress, which it silently ignored before, so uploaded snapshots can be styled too. - An unparseable color falls back to its default instead of dropping the whole popup.
- Verified on hardware: a Fire TV stick (Fire OS, Android 9) sleeps via device admin; a Nokia Streaming Box 8010 (Android 14) has no device-admin feature at all and sleeps via the accessibility route.
- If you script the accessibility grant yourself: append to
enabled_accessibility_services, don't overwrite it — replacing the value disables other accessibility services.
v0.6.2 — 2026-07-28 (the service restarts itself after an update)
- The app listens for
MY_PACKAGE_REPLACED, so the service comes back by itself after a self-update instead of staying down until someone opened the app.
v0.6.1 — 2026-07-27 (crash fix on repeated start requests)
startForeground()is now called on every start command, not just the first — Android kills a foreground service that skips it.
v0.6.0 — 2026-07-27 (self-update from GitHub releases)
- The app can update itself from the GitHub releases of this fork.
v0.5.0 — 2026-07-27 (status screen & last popup in /state)
- A status screen on the TV, and the last popup exposed in
/state.
v0.4.0 — 2026-07-26 (lazy TTS & robust webserver start)
- TTS is initialised lazily and the webserver survives a rough start.
v0.3.2 — 2026-07-19 (thread-safety & callback fixes)
- Thread-safety around the popup queue and the button callbacks.
- Popup buttons, progress bar and the
urgencypresets (info/warning/critical).
- The first fork releases:
/stateendpoint with popup visibility and screen state, a popup counter, the dismiss watchdog (watchdogCleanups), and the groundwork the later versions build on.