Target api 36 - #233
Open
patriciojofre wants to merge 9 commits into
Open
Conversation
R.layout.welcomebatch only existed under layout-port, layout-small-port, layout-large-port and layout-xlarge-port. WelcomeBatchActivity requests a portrait lock before calling setContentView, so the missing configuration was unreachable until now — but Android 16 (API 36) ignores that lock on displays with smallest width >= 600dp, which would make the lookup fail with Resources$NotFoundException on tablets in landscape. Add a default layout/welcomebatch.xml that centers the logo with gravity="center" plus maxHeight/adjustViewBounds instead of the fixed marginTop="300dp" the portrait variants use, so it holds up at any window height. The existing -port variants are untouched, so portrait is unchanged. Also delete device_ready, warning and warning_android7: port-only layouts with no base variant whose referencing activities were removed long ago (DeviceReadyActivity in 89ffcaa, R.layout.warning in 9b44e8f). They were the remaining MissingDefaultResource fatals. Lint fatals: 11 -> 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Google Play requires the target API level to stay within one year of the latest Android release: from Aug 31, 2026 an app targeting below API 36 can no longer be updated. Raise compileSdk and targetSdk to 36 in the Play build, along with the COMPILE_SDK_VERSION field reported to the API as hardware telemetry. AGP 8.13.2 and Gradle 8.13 already support API 36, so no toolchain change is needed. Verified with a clean build: assembleDebug, lintDebug and testDebugUnitTest all pass. build.gradle.internal deliberately stays on 33 — the wipe capability it relies on is blocked at higher target API levels, and that variant ships through an internal process rather than Play, so the requirement does not apply to it. A comment now records this so it is not bumped by mistake. Note: build.gradle.dev is gitignored, so its matching bump to 36 is local only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
R.layout.welcomebatch shipped for years with variants only under layout-port and friends. That stayed invisible because every activity in the app requests a portrait lock, so the missing configuration was unreachable — until API 36 made Android ignore that lock on large screens. Pin the invariant with a Robolectric test that resolves layouts under landscape qualifiers. It needs no MDM enrollment, no network and no emulator: resource resolution is a function of the resource name and the device configuration only, so a qualifier reproduces the failure exactly. getLayout performs the same configuration lookup as inflation without needing a theme, a parent view or resolvable drawables. Beyond asserting welcomebatch specifically, the test sweeps every R.layout entry and reports all misses at once, so the next layout added under a narrow-only qualifier fails CI with its own name instead of crashing on a user's tablet. Verified the guard actually fails: removing layout/welcomebatch.xml turns 3 of the 5 tests red with the same Resources$NotFoundException the app would throw. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The manifest sets android:enableOnBackInvokedCallback="true", so from Android 13 on the platform dispatches back through OnBackInvokedDispatcher and stops calling Activity.onBackPressed(). Five activities only overrode onBackPressed, so their back handling had been dead on modern devices: BarcodeActivity, PanelWebActivity, ReportActivity and SecurityActivity silently stopped returning to the password screen, and PermissionInformationActivity — whose empty override exists to keep the user inside the permission flow — silently stopped blocking back at all. ReportActivity and SecurityActivity extend AndroidX activities, so they register an OnBackPressedCallback, which covers both the old and the new path. The other three extend the platform Activity directly and have no OnBackPressedDispatcher. Rather than change their base class, they register with OnBackInvokedDispatcher on Android 13+ through the new BackNavigationCompat helper and keep their onBackPressed override for API 21-32, where enableOnBackInvokedCallback is ignored. Those remaining overrides carry a SuppressLint with that reason, so the lint check keeps flagging real cases. Lint GestureBackNavigation errors: 5 -> 0. MissingSuperCall: 2 -> 0. Not runtime-verified: this needs a device pass on Android 13+ to confirm back lands where intended, especially that PermissionInformationActivity still refuses to go back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…itly From Android 14 on, registerReceiver without RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED throws SecurityException. Three call sites had no flag: - PopUpAlertActivity registered inside a try/catch that only logged, so on Android 14+ the popup's receivers were never registered and the alert could no longer be dismissed remotely — a silent functional failure. - PreyDisablePowerOptionsService registered with no catch at all, in both onStart and onStartCommand, so the service went down every time the disable-power-options feature was enabled. Route all of them through ContextCompat.registerReceiver, which picks the right overload per API level. The flag values are not uniform, because the broadcasts are not: - close_prey and popup_prey_* are only ever sent from inside the app, so they are now RECEIVER_NOT_EXPORTED. CheckPasswordHtmlActivity previously used RECEIVER_EXPORTED for close_prey, which let any installed app dismiss the password screen by broadcasting a bare action string. - ACTION_APPLICATION_RESTRICTIONS_CHANGED comes from the system when the EMM changes restrictions, so it stays exported. - ACTION_CLOSE_SYSTEM_DIALOGS is a system broadcast and stays exported too, though the platform has not delivered it to apps since Android 12. Making those receivers non-exported exposed the matching problem on the send side: the app broadcast bare implicit intents that any app with a matching receiver could pick up. close_prey now goes through CheckPasswordHtmlActivity.broadcastClosePrey, which scopes the intent with setPackage, and AlertReceiver does the same for the popup action. Lint UnspecifiedRegisterReceiverFlag errors: 4 -> 0, UnsafeImplicitIntentLaunch back to 0. Clean build, lint and 93 unit tests all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds instrumented coverage for the two things API 36 changed for this app, run against a Pixel Tablet AVD on Android 16 (2560x1600 @ 320dpi, smallestWidth 800dp) where the platform reports ignoreOrientationRequest=true — so the portrait lock every activity here requests really is being ignored. Resource resolution: welcomebatch, plus a sweep over every R.layout entry, are resolved in the configuration the device is actually in. The Robolectric guard already covers this with qualifiers; this runs it against the real resource resolution on a real large screen. Back navigation: one activity per implementation, SecurityActivity through AndroidX's OnBackPressedDispatcher and PanelWebActivity through BackNavigationCompat and the platform dispatcher. The assertion is on the destination rather than on the launched activity finishing, because the platform's default back also finishes it — a test that only checked for finishing would pass with the handler dead. Confirmed both fail when the registration is removed. These are instrumented rather than local tests because non-exported activities cannot be started from the adb shell on Android 16 (SecurityException: not exported), while instrumentation runs as the app's own UID. The lock screens are deliberately not covered, with the reason recorded in the file: launching one cold trips the app's own guard, which starts CloseActivity and tears the lock down within a second. That is correct behaviour, so such a test would fail for a reason unrelated to rotation. Confirming the lock survives a rotation still needs a real lock command from the panel and a physical rotate. Full instrumented suite: 52 tests green on the tablet AVD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
github-code-quality flagged largeScreenLandscapeQualifiersAreDeclared as a useless comparison, and it was right: it asserted that a literal array's length was >= 2, which cannot fail. The array it checked was not used by anything either — the sweeps take their configuration from their own @config qualifiers — so it was dead data that could drift from the tests it claimed to describe. Both are gone. The instrumented test had the same defect unflagged: assertTrue(smallestWidthDp > 0) is true on any device. Replaced with an assertion that can actually fail — PanelWebActivity declares screenOrientation="portrait", so on a large screen running API 36 it must come up in landscape anyway. The preconditions are now assumptions, so on a phone AVD or a tablet held in portrait the test reports as skipped rather than passed, and a green suite cannot be read as coverage it did not provide. Verified both ways: passes in landscape, skips in portrait. Rotating the tablet to portrait to check that also surfaced a real false positive in the sweeps. R.layout holds the merged resources of every dependency, and some library layouts are config-scoped by design — material_clock_period_toggle_land exists only for landscape — so the sweep failed on a Material layout that is not ours to fix. The sweeps now skip third-party layouts by name prefix, a list verified to cover all 108 library layouts while excluding none of the 38 the app owns, and the failure message reports both counts so drift stays visible. If a dependency later adds a config-scoped layout under a new prefix the test fails naming it, which is the right direction to fail in. Re-confirmed the guard still bites after the change: hiding layout/welcomebatch.xml fails 3 of 4 Robolectric tests with "1 of 38 app layout(s) do not resolve ... (108 library layouts skipped)". 92 unit tests and 52 instrumented tests green on the tablet AVD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI ran the emulator on API 30 with the runner's phone-sized AVD, so it never exercised the change the app is being migrated for: below 600dp of smallest width API 36 still honours the portrait lock, and the large-screen tests reported as skipped while the job went green having covered nothing. Adds an api-level matrix. API 36 is what the app targets; API 30 stays because part of the code only runs below Android 13 — BackNavigationCompat falls back to onBackPressed there, and the three plain-Activity overrides are live only on that path — so dropping it would leave that untested. fail-fast is off so one failing level still reports the other. On the 36 leg the script resizes the display to 2560x1600 at 240dpi, giving 1066dp of smallest width in landscape, and sets ignore-orientation-request. Verified locally against both configurations rather than assumed: with phone metrics (1080x2400 @ 420dpi, 411dp) portraitLockIsIgnoredOnThisDisplay reports skipped, and with the metrics above it runs and passes, 0 skipped, with the platform reporting ignoreOrientationRequest=true. Also guards that test by API level. It assumed a large screen in landscape and then asserted the portrait lock was ignored, which is false below API 36 — so on the new API 30 leg it would have failed for a reason unrelated to any defect. It now skips there instead. Artifact names are per API level, since upload-artifact@v4 rejects two uploads under one name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The API 30 leg failed before running anything:
/usr/bin/sh -c if [ "30" -ge 36 ]; then
/usr/bin/sh: 1: Syntax error: end of file unexpected (expecting "fi")
reactivecircus/android-emulator-runner executes each line of an inline script in
its own shell, so only the first line of the `if` ever reached sh. Any multi-line
construct inline is unparseable, regardless of API level — the 36 leg would have
failed the same way.
Moves the logic to .github/scripts/run-instrumented-tests.sh, invoked as a single
line with the API level as an argument, and adds a guard for a missing argument so
a future edit to the workflow fails loudly instead of silently skipping the resize.
Verified rather than assumed, against a live API 36 emulator:
- `run-instrumented-tests.sh 36` applies the large-screen configuration, the
platform reports ignoreOrientationRequest=true, and all 52 instrumented tests
pass with 0 skipped.
- `run-instrumented-tests.sh 30` takes the phone-sized branch and the suite still
runs — this is the path that was broken.
- The original failure reproduces locally by feeding just the first line to sh,
which is what confirmed the diagnosis.
- Syntax checked with both `sh -n` and `dash -n`, dash being /usr/bin/sh on the
runner.
The file is committed as 100755; without the exec bit the runner cannot invoke it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.