Is your Android app ready for targetSdk 36 and 37? A fast static scanner and GitHub Action that finds the code, manifest
entries and resources affected by the Android 16 (API 36) and Android 17 (API 37) behavior changes: edge-to-edge
enforcement, predictive back (onBackPressed, KEYCODE_BACK), large-screen orientation and resizability restrictions,
the new local network permission, background-activity-launch modes and more. It also checks the Google Play target API
level requirement (36 for phones since 2026-08-31, with Wear OS / TV / Automotive floors).
- Works on Kotlin, Java, Gradle (Groovy and Kotlin DSL), version catalogs, manifests and resource XML.
- No Gradle run, no SDK, no dependencies: Python 3.9+ standard library only. A big repository scans in seconds.
- Output as text, Markdown, JSON, GitHub annotations or SARIF (code scanning).
- Every rule links to the official Android documentation page that describes the change.
- Honest about its limits: see Limitations and docs/precision.md.
| Lite (try it in a minute) | Full (keep it in CI) | |
|---|---|---|
| How | pip install git+https://github.com/cosmichackerx/android-target-ready then android-target-ready . |
the GitHub Action (job summary, SARIF), --base REF PR mode and the ignore comments |
Every number below is from this repository's own tests or scripts (see the linked sections). "Not proven" is as important as "Result".
| What is claimed | Checked against | Size | Result | Not proven |
|---|---|---|---|---|
| A finding is the construct the Android docs describe | Hand check of random findings on a 100-repository corpus (docs/precision.md) | 40 findings, then 40 more with a different seed after fixes | 33/40 (82.5 %) before the fixes, 39/40 (97.5 %) after | Rules were tuned on the same corpus, so 97.5 % is optimistic; the author read the findings; the Android docs are the reference, not a device run |
| It agrees with a type-resolving linter | android-target-lint on 52 shared repositories (six overlapping rules) | 52 repositories | 71 % agreement on the combined findings | Disagreement is not an error count; neither tool is ground truth |
| Recall | - | - | Not measured | Pattern-based, line by line, no type resolution. There is no oracle: Android Lint and a run on an Android 16/17 device stay authoritative |
| Rule logic | Unit tests on Linux, Windows, macOS | 46 tests | green | - |
Releases: 5 releases, v0.1.0 to v0.2.3, all published between 2026-10-02 and 2026-10-03 (days old). See CHANGELOG.md and the Releases page. There is no weekly watcher for this tool: when Android's behaviour-change pages change, the rules are updated by hand.
pip install git+https://github.com/cosmichackerx/android-target-ready
android-target-ready path/to/your/project
or without installing: python -m android_target_ready . from a checkout (PYTHONPATH=src).
Real output on TeamNewPipe/NewPipe (a shallow clone on 2026-10-03; this is a scan of public code, not a statement about the project):
app/build.gradle.kts
47 error play-target-floor (API 36) Application module `app` targets 35; Google Play requires 36 or higher for phone app updates since 2026-08-31 (extension requests to 2026-11-01).
> release(NEWPIPE_VERSION_SDK_TARGET)
app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java
130 warning back-pressed-override (API 36) onBackPressed() is not called on Android 16 devices once the app targets 36 (predictive back is on by default). Use OnBackPressedCallback.
> public void onBackPressed() {
app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
122 warning back-pressed-override (API 36) onBackPressed() is not called on Android 16 devices once the app targets 36 (predictive back is on by default). Use OnBackPressedCallback.
> override fun onBackPressed() {
app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
140 warning back-pressed-override (API 36) onBackPressed() is not called on Android 16 devices once the app targets 36 (predictive back is on by default). Use OnBackPressedCallback.
> public void onBackPressed() {
app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java
48 warning back-pressed-override (API 36) onBackPressed() is not called on Android 16 devices once the app targets 36 (predictive back is on by default). Use OnBackPressedCallback.
> public void onBackPressed() {
app/src/main/res/values-v35/styles.xml
6 error edge-to-edge-opt-out (API 36) windowOptOutEdgeToEdgeEnforcement=true: disabled on Android 16 devices once the app targets 36.
> <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
931 source/manifest/resource file(s) scanned for target 37; application modules: app: targetSdk 35. 2 error, 4 warning, 0 info.
The play-target-floor error is the Play requirement for phone apps; edge-to-edge-opt-out is the documented temporary opt-out
that stops working once the app targets 36; the onBackPressed warnings are activities and dialogs that will not get the callback
on Android 16 devices once the app targets 36 (use OnBackPressedCallback). A small project that is ready:
No target 37 migration findings.
2 source/manifest/resource file(s) scanned for target 37; application modules: app: targetSdk 37. 0 error, 0 warning, 0 info.
name: android-target-ready
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: cosmichackerx/android-target-ready@v0.2.3
with:
target: "37" # 36 or 37
fail-on: error # error | warning | neverInputs: path, target, fail-on, disable, ignore, include-tests, summary (job summary Markdown), sarif-file
(write SARIF, then upload with github/codeql-action/upload-sarif). Findings appear as annotations on the pull request.
On a pull request you usually care about what the change adds, not the backlog. With pr-mode the Action (or --base REF on the command line) scans the base revision too and reports only findings that are new. Findings are matched by rule, file and the normalised source line, not the line number, so moving code around does not make old findings look new; a second copy of an existing line does count as new.
name: android-target-ready
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # only for the sticky comment
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # the base branch must be in the clone
- uses: cosmichackerx/android-target-ready@v0.2.3
with:
comment: "true" # implies pr-mode; one comment, updated in place
fail-on: error # only NEW findings can fail the pull requestThe comment is created on the first new finding, updated in place afterwards (marker <!-- android-target-ready:sticky -->), and rewritten to "No new findings." once they are fixed; a clean pull request that never had a comment gets none. Pull requests from forks have a read-only token, so the comment is skipped with a notice there (annotations and the job summary still work). Real comment from this repository's own CI (pull request #9):
android-target-ready: 3 source/manifest/resource file(s) scanned for target 37; application modules: app: targetSdk 37. 0 error, 1 warning, 0 info. PR mode against
origin/main: 1 new, 0 already present (not shown), 0 resolved. | warning |back-pressed-override| 36 |app/src/main/java/Added.kt:2| onBackPressed() is not called on Android 16 devices once the app targets 36 ... |
Extra inputs: pr-mode, base (default origin/<base branch>), comment, github-token.
android-target-ready [path] [--target 36|37] [-f text|markdown|json|github|sarif] [-o FILE]
[--fail-on error|warning|never] [--disable RULE] [--ignore GLOB]
[--include-tests] [--base REF] [--list-rules]
Exit code 1 when a finding at or above --fail-on exists (default error), 2 on usage errors.
Suppress a finding in code with a comment on the same or the previous line: // android-target-ready: ignore back-keycode
(ignore all silences every rule on that line). Test source sets are skipped unless --include-tests. Games
(android:appCategory="game") are exempt from the large-screen rules, as the platform documents.
Severity can be higher at 37 where a temporary opt-out disappears.
| Rule | API | Severity | What it means | Docs |
|---|---|---|---|---|
play-target-floor |
36 | error | targetSdk is below what Google Play requires for new apps and updates (36 since 2026-08-31; Wear OS 35, Android TV/XR 34). | docs |
target-unresolved |
- | info | The targetSdk of an application module could not be determined statically (computed in a build script?). | docs |
edge-to-edge-opt-out |
36 | error | windowOptOutEdgeToEdgeEnforcement=true: disabled on Android 16 devices once the app targets 36. | docs |
back-pressed-override |
36 | warning | onBackPressed() is not called on Android 16 devices once the app targets 36 (predictive back is on by default). Use OnBackPressedCallback. | docs |
back-keycode |
36 | warning | KeyEvent.KEYCODE_BACK is not dispatched on Android 16 devices once the app targets 36. Use OnBackPressedCallback. | docs |
back-opt-out |
36 | info | android:enableOnBackInvokedCallback="false" is the documented temporary opt-out from predictive back. | docs |
elegant-text-height |
36 | warning | elegantTextHeight is ignored once the app targets 36 (the UI-font APIs are discontinued). | docs |
fixed-rate-scheduling |
36 | info | scheduleAtFixedRate: at most one missed execution runs when the app returns to a valid lifecycle (target 36). | docs |
body-sensors-permission |
36 | warning | BODY_SENSORS / BODY_SENSORS_BACKGROUND are replaced by android.permissions.health.* permissions for apps targeting 36. | docs |
fixed-orientation |
36 | warning → error at 37 | android:screenOrientation with a portrait/landscape value is ignored on screens >= sw600dp (target 36; no opt-out at 37). | docs |
set-requested-orientation |
36 | warning → error at 37 | setRequestedOrientation()/requestedOrientation with a portrait/landscape value is ignored on screens >= sw600dp (target 36; no opt-out at 37). | docs |
non-resizeable |
36 | warning → error at 37 | android:resizeableActivity="false" has no effect on screens >= sw600dp (target 36; no opt-out at 37). | docs |
aspect-ratio-limit |
36 | warning → error at 37 | android:minAspectRatio / maxAspectRatio have no effect on screens >= sw600dp (target 36; no opt-out at 37). | docs |
large-screen-opt-out |
36 | info → error at 37 | PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY is the temporary opt-out; it does not apply when targeting 37. | docs |
local-network-permission |
37 | warning | Local network access (NsdManager, multicast, mDNS/SSDP) needs the ACCESS_LOCAL_NETWORK runtime permission when targeting 37. | docs |
bal-legacy-mode |
37 | warning | MODE_BACKGROUND_ACTIVITY_START_ALLOWED is being replaced by granular modes such as MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE. | docs |
content-capture-disable |
37 | warning | setContentCaptureEnabled(false) no longer disables Content Capture when targeting 37; use FLAG_SECURE. | docs |
native-load-writable |
37 | info | Files loaded with System.load() must be read-only when targeting 37, otherwise UnsatisfiedLinkError. | docs |
static-final-reflection |
37 | warning | Modifying static final fields through reflection throws IllegalAccessException when targeting 37. | docs |
message-queue-reflection |
37 | warning | android.os.MessageQueue has a new lock-free implementation when targeting 37; reflection on its private members may break. | docs |
contacts-pii-columns |
37 | warning | ACCOUNT_NAME / ACCOUNT_TYPE / ACCOUNT_TYPE_AND_DATA_SET are removed from the ContactsContract.Data view when targeting 37; read them from RawContacts. | docs |
rfcomm-read |
37 | info | RFCOMM BluetoothSocket InputStream.read() returns -1 on close when targeting 37; loops that only catch IOException may not terminate. | docs |
remoteviews-bitmap |
37 | info | Bitmaps/Icons in a RemoteViews parcel are capped at 1.5 x screen width x height x 4 bytes when targeting 37; exceeding it crashes the process. | docs |
sms-otp-delay |
37 | info | Standard OTP SMS messages reach most apps only after three hours when targeting 37 (SMS Retriever / User Consent are exempt). | docs |
How targetSdk is found: literals, libs.versions.toml, gradle.properties, ext/extra, Kotlin const val,
AGP 9 targetSdk { version = release(..) }, and, when the module's own build file does not say, heuristics in this order: a precompiled script plugin named like the plugin id the module applies, a convention plugin class whose name matches the applied alias, all of build-logic/buildSrc/build-plugin when they agree on one value, and shared root scripts (common.gradle, subprojects {}) when they agree. Several values in one file (product flavors) report the lowest. If it cannot be determined
you get an info finding (target-unresolved) instead of a guess.
- Android Lint knows the project model, runs on the compiled sources and is authoritative; it covers some of these changes
(for example edge-to-edge and API-level checks) when you actually raise
targetSdk. android-target-ready is for the question before you raise it: "what will this cost?", across a repository, in seconds, without building. - Google's
check_elf_alignment.shand other 16 KB page-size checkers look at native libraries; this tool does not. - Play-policy preflight tools (for example GPC Preflight) work on a built app bundle and manifest; in a search on 2026-10-02 I found none that scans source for these behaviour changes. That is a search result, not a guarantee.
- Pattern based, line by line. No type resolution (the sibling android-target-lint resolves types; on 52 shared repositories the two agreed on 71 % of the combined findings of six overlapping rules, see its comparison): an
onBackPressed()is only counted in files that extend a class called*Activityor*Dialog; a class hierarchy through a differently named base class is missed. - Does not run Gradle, so targetSdk set by custom plugins, product flavors or
-Pflags can be unresolved or wrong. - Not every Android 16/17 change is detectable statically; only the rules above exist. Recall is not measured.
- Measured on 100 public repos with a 40-finding hand-check (97.5 % after fixes, 82.5 % before): see docs/precision.md. That is a sample read by the author, not a benchmark.
- Rules reflect the official documentation as fetched on 2026-10-02; Android 17 details can still change.
pip install -e . pytest && pytest -q
CI runs the tests on Ubuntu, Windows and macOS (Python 3.9, 3.11, 3.13), builds the wheel/sdist and self-tests the Action. See CONTRIBUTING.md, CHANGELOG.md and SECURITY.md.
Small, independent tools by the same author, for build and CI hygiene and for migrations with a deadline. Each works on its own; none requires another.
Gradle and Android migrations
- gradle-version-catalog-lint: Lints
libs.versions.toml: unused libraries, plugins and versions, dynamic or SNAPSHOT versions, hard-coded dependencies. - gradle10-ready: Static scan of Gradle build scripts for what Gradle 10 removes (space assignment, multi-string dependencies, Kotlin DSL delegates).
--fix, PR mode. - agp9-ready: Static scan of Gradle files for what Android Gradle Plugin 9 and 10 break (built-in Kotlin, legacy variant API, opt-outs), including
buildSrc.--fix, PR mode. - kotlin24-ready: Static scan of Gradle build scripts for what Kotlin 2.4 removes in the Kotlin Gradle plugin (language version 1.9, KMP
targetHierarchy, Compose options, ABI validation).--fix, PR mode. - android-target-lint: The same targetSdk migration checks as real Android Lint rules (a lint jar with type resolution).
CI and repository hygiene
- node24-ready: Finds GitHub Actions still on the removed Node 20 runtime, also inside composite actions and reusable workflows, and the smallest node24 upgrade.
- dependabot-gaps: Finds manifests your
dependabot.ymldoes not cover, and dead or overlapping entries. - sha256-ready: Finds code that assumes 40-character Git hashes before Git 3.0 makes SHA-256 repositories the default.
- helm4-ready: Finds the Helm 3 CLI usage (removed and deprecated flags, executable post-renderers,
registry loginURLs, Helm 3 pins) that Helm 4 rejects in CI workflows, scripts and Makefiles, checked against real Helm 3.22.0 and 4.3.0. - kafka4-ready: Finds Kafka 3 settings and CLI usage that Kafka 4 rejects or silently ignores (ZooKeeper-mode broker files, removed
zookeeper.*andlog.message.format.versionsettings,--zookeeperoptions, space-separated--bootstrap-server), checked against a real Kafka 4 broker and tools. - pandas3-ready: Static scan of Python files and notebooks for code that breaks or changes meaning on pandas 3 (removed APIs and aliases, chained assignment, read-only arrays, string dtype), checked against pandas 2.3.3 and 3.0.x.
- agent-context-diff: Diffs
AGENTS.md,CLAUDE.md, Cursor rules and MCP configs between git refs (new servers, widened permissions, hidden Unicode).
MIT