screentinker/android/app/src/main
ScreenTinker 3333d5e968 Stop Android panels losing controls when they update
A declared capability set REPLACES the per-platform baseline rather than
merging with it, so anything the baseline grants and the player omits is a
control the operator loses by updating. Three were being lost.

- display.brightness: the per-window dim (setWindowBrightness) is Tier 0 —
  no permission, no owner, no WRITE_SETTINGS — and MainActivity applies it
  unconditionally. It was simply never declared.

- remote.screenshot / remote.stream: gated on the accessibility service,
  while captureScreen() falls through to ScreenshotCapture.captureView,
  a plain view draw with no permission check. A Tier-0 panel lost live view
  and screenshots by updating, and a GRANTED MediaProjection never became a
  capability either — consent given, capture working, server still refusing,
  because nothing re-declared.

- system.device_owner: no player declared it, so the server accepted
  system.kiosk as a stand-in for every Tier-2 command. Declaring the
  canonical name makes refusals say what they mean; the stand-in can retire
  one release after this reaches displays.

display.power stays conditional on purpose: screen_on works anywhere via a
wake lock but screen_off needs owner/admin/accessibility, and a control that
sleeps a panel it cannot wake is worse than no control. It is the sole entry
in the DELIBERATE set in player-parity-baselines.test.js.

Also fixes the capture-bootstrap gate in device-detail.js. It hung off
can('remote.screenshot'), which hid the button from exactly the panels that
need it. The gate is now Android-and-nothing-else, NOT "Android that lacks
capture": /api/devices/:id ships capabilitiesFor(), which flattens declared
and baseline into one array, and the android baseline contains
remote.screenshot — so a "lacks capture" test hides the button from all ~440
undeclared panels in the field. isAndroidDevice() mirrors platformFamily()
with all four signals in order; an Android-test-only helper classified every
Tizen TV as Android, since Tizen registers android_version 'Tizen 6.5'.

Tests: the suite could not see any of this. Mutation testing showed deleting
either capability line, or reverting isAndroidDevice to its buggy form, left
all tests green. Added an update-invariant test (declared set vs baseline,
with an argued exception list), a test that executes the shipped helper
rather than the harness stub, and a legacy-panel test using the shape the API
actually returns instead of one it never produces. All four mutations now
fail.

Verified on a real Android 16 device across all three tiers: Tier 0 captures
live video (no accessibility, no MediaProjection, no owner), Tier 1 gains
display.power via accessibility, Tier 2 declares system.device_owner and every
Tier-2 command delivers. An in-place upgrade from the pre-change build lost
nothing and gained exactly these three.

Baselines deliberately NOT moved — a baseline entry moves in the release
AFTER the one carrying the player fix, once it has reached displays.

Parity gaps 3 and 4 were implemented, audited and reverted; docs/player-parity.md
records why so the next attempt starts from the traps. Gap 3 (wiring "Force
update") meets an unbounded synchronous download against a 120s watchdog and a
3-attempt counter with no version binding, so three presses refuse a panel every
future version. Gap 4 (deferring to BS.capabilities()) removes working
screenshot/stream from diskless BrightSigns that capture to RAM, over-declares
transitions, and rides a probe timeout that discards a late answer permanently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bvjey4FNam49MN7ybjcq6A
2026-08-10 10:31:33 -05:00
..
java/com/remotedisplay/player Stop Android panels losing controls when they update 2026-08-10 10:31:33 -05:00
res Android: the boot notice now clears, and kiosk survives a reboot 2026-08-05 13:30:48 -05:00
AndroidManifest.xml Report the screen's own IP, and make the Wi-Fi name an honest optional 2026-07-29 21:57:59 -05:00