Installing your app on a device running the newest Android version is not the same as testing the newest Android version. A build running on Android 16 behaves under one set of platform changes, and a subset of those changes only switches on once your targetSdkVersion reaches 36. Two test questions hide inside one device.
That distinction is what makes Android 16 compatibility testing practical. Google ships a mechanism that turns individual behaviour changes on and off for a single app, without editing the manifest or recompiling. It is the compatibility framework, reachable from Developer Options and ADB. Used well, it turns "targeting 36 broke something" into "change 357141415 is responsible for this one screen".
This guide covers that workflow: the kinds of compatibility changes, what the framework can and cannot toggle, the exact ADB commands, what CompatibilityChangeReporter writes to Logcat, and how to produce a report someone else can act on. It assumes you already know your app must reach API 36 — Google Play's Android 16 target API requirements cover that policy side.
What Is Android App Compatibility Testing?
Platform compatibility changes come in two shapes, and the difference decides how you test them.
Changes that affect all apps apply whenever the app runs on that Android version, whatever its target SDK is. Android 16's JobScheduler quota changes are of this kind. If you install your existing build on Android 16 and a background job stops running on schedule, that is an all-apps change, and nobody raised a target SDK to cause it.
Changes gated by targetSdkVersion switch on only when your app declares the relevant API level or higher. Edge-to-edge enforcement, the large-screen orientation and resizability rules, and the fixed-rate scheduling change are in this group for apps targeting 36 — the changes that arrive as a batch the week you bump the target, which is why teams mistake a platform shift for a regression they introduced.
The practical consequence: you can test all-apps changes today on an Android 16 device with your current build, and you can test target-gated changes before you change anything, by forcing the individual toggles.
None of this is Google Play submission compliance. Compliance asks whether the uploaded artifact declares the required target level; compatibility testing asks whether the app still behaves correctly under the platform changes it will meet in the field. An app can pass the first and fail the second in the same release.
| Change type | What it means | How to test | Example |
|---|---|---|---|
| Applies to all apps | Active as soon as the app runs on that Android version, regardless of targetSdkVersion |
Install the current build on Android 16 and run your normal tests | JobScheduler quota adjustments |
Gated by targetSdkVersion |
Enabled by default only when the app targets the gating API level or higher | Force the change on with a toggle while still targeting the old level | Orientation and resizability restrictions ignored on sw600dp+ displays |
| Logged only | Reported through the framework but not switchable; may still change behaviour | Watch Logcat, read the change description, test the affected API | Changes marked LOGGED in the reporter output |
What Is the Android Compatibility Framework?
The compatibility framework is the part of the platform that decides, per app, whether a behaviour change is active. Google introduced the developer tooling around it in Android 11: the ability to turn breaking changes on and off individually through Developer Options or ADB.
The important sentence is in Google's own documentation: because the platform adapts its internal logic, you do not need to change your targetSdkVersion or recompile your app to perform basic testing. That is what makes the tool useful before a migration rather than after it — you can force a gated change on, watch what breaks, and fix it while the app still declares the old target.
Two constraints decide whether the toggles are available at all. The app must be debuggable — a release build installed from Play does not expose them. The device build matters: on a public user build a debuggable app can toggle changes gated by targetSdkVersion, but changes that apply to all apps cannot be toggled there, whereas a preview or beta device build allows both. Understand this before planning a session, because it determines which questions the framework can answer on the hardware you have.
| Device build | App type | Changes gated by targetSdkVersion |
All other changes |
|---|---|---|---|
| Developer Preview or Beta | Debuggable | Can toggle | Can toggle |
| Developer Preview or Beta | Non-debuggable | Cannot toggle | Cannot toggle |
| Public user build | Debuggable | Can toggle | Cannot toggle |
| Public user build | Non-debuggable | Cannot toggle | Cannot toggle |
How to Open App Compatibility Changes in Android 16
Google documents this path for a device running Android 16:
- Build and install a debuggable variant of your app on the Android 16 device or emulator.
- If Developer Options are not already on, enable them.
- Open Settings and navigate to System › Advanced › Developer options › App Compatibility Changes.
- Select your app from the list.
- Review the groups: changes enabled by default, changes enabled only for apps targeting a level (shown as something like "Enabled for targetSdkVersion >= 36"), and a Default Disabled Changes section.
- Find the one change you intend to test and use its switch.
- The app is killed when you toggle, so relaunch it deliberately rather than being surprised.
- Reproduce the affected workflow, then record the result.
Two caveats. Menu wording and placement vary between manufacturers and Android versions. And toggling a change you do not understand is a bad habit: read its description in the Android 16 compatibility framework list first, because the Default Disabled section exists for changes that are not meant to be flipped casually.
How to Test Android 16 Compatibility Changes Using ADB
Everything the Developer Options screen does, ADB can do faster and scriptably. Confirm the device is visible first:
adb devices
The rest follows the framework's own syntax. These four commands are the core, and each accepts either the numeric change ID or the change name:
# whole-device view of every change, enabled and disabled
adb shell dumpsys platform_compat
# force one change on for one package
adb shell am compat enable (CHANGE_ID|CHANGE_NAME) PACKAGE_NAME
# force one change off for one package
adb shell am compat disable (CHANGE_ID|CHANGE_NAME) PACKAGE_NAME
# drop the override and return to the default state
adb shell am compat reset (CHANGE_ID|CHANGE_NAME) PACKAGE_NAME
In those commands, PACKAGE_NAME is the applicationId of the installed build — the value Play Console shows as the package name, for example com.example.notes. CHANGE_ID is the numeric identifier from the official list, and CHANGE_NAME is its constant, such as UNIVERSAL_RESIZABLE_BY_DEFAULT. Either works; the name reads better in a script, the ID pastes neatly from a log line.
Android 16 also documents two change-specific overrides for the JobScheduler quota work, which you can enable for your package when diagnosing background work:
adb shell am compat enable OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS PACKAGE_NAME
adb shell am compat enable OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS PACKAGE_NAME
Standby-bucket behaviour uses commands that take a bucket argument rather than an ID:
adb shell am set-standby-bucket PACKAGE_NAME active|working_set|frequent|rare|restricted
adb shell am get-standby-bucket PACKAGE_NAME
| Command | Purpose | When to use it |
|---|---|---|
adb devices |
List connected devices | First command of any session, before blaming a toggle |
dumpsys platform_compat |
Inspect framework state for the whole device | Confirm which changes exist, their gating level and any package overrides |
am compat enable |
Override a change to on for one package | Test a gated change before raising targetSdkVersion |
am compat disable |
Override a change to off for one package | Isolate which of several changes is causing a failure |
am compat reset |
Remove an override | End of every test, so the next result is not contaminated |
set-standby-bucket / get-standby-bucket |
Read or force the app standby bucket | Background-job tests that depend on quota and bucket |
These commands have two limits. They act on a package on a connected device, so they are useless against a release build installed by a user — the debuggable requirement applies to ADB exactly as it does to the on-screen switches. And a change the device build does not allow you to toggle simply will not accept the override; that is the restriction table above, not a broken command.
Understanding Android 16 Compatibility Change IDs
A change ID is a unique number identifying one behaviour change inside the framework — not a document section, an API level or a version. The same ID is what the toggle screen lists, what dumpsys platform_compat prints, what Logcat reports, and what am compat accepts, which is what makes it a usable thread to pull on when a feature misbehaves.
These verified Android 16 entries come from Google's official list, with their documented default states:
| Change name | Change ID | Documented default and what it controls |
|---|---|---|
UNIVERSAL_RESIZABLE_BY_DEFAULT |
357141415 | Enabled for apps targeting Android 16. Orientation, resizability and aspect ratio constraints ignored on screens larger than 600dp |
STPE_SKIP_MULTIPLE_MISSED_PERIODIC_TASKS |
288912692 | Enabled for apps targeting Android 16. At most one missed scheduleAtFixedRate execution runs when the app returns to a valid lifecycle |
FORCE_RESIZE_APP |
174042936 | Disabled for all apps. Forces the packages it is applied to to be resizable, in fullscreen windowing mode |
FORCE_NON_RESIZE_APP |
181146395 | Disabled for all apps. Forces the packages it is applied to to be non-resizable |
OVERRIDE_ANY_ORIENTATION |
265464455 | Disabled for all apps. Gatekeeper that allows the orientation overrides below it to apply regardless of the orientation an activity requested |
OVERRIDE_MIN_ASPECT_RATIO |
174042980 | Disabled for all apps. Gatekeeper for the minimum aspect ratio overrides, which must also be enabled |
Three states appear in the reporter, and one is easy to misread. ENABLED means the change is active and will affect the app if it calls the affected APIs. DISABLED means it is not affecting the app — and Google notes that a change disabled because the app's target is below the threshold will switch on by itself once the target rises. LOGGED means the framework reports the change but cannot toggle it; it may still affect behaviour, and many such changes are experimental. Do not assume LOGGED is harmless without reading the description.
Always verify a change against the official Android 16 list rather than a list in a blog post, including this one. The set of changes and their default states differ between Android releases, so read the list for the version you are testing.
How to Find Compatibility Problems in Logcat
The first time your app calls an API affected by a change, the system writes a line tagged CompatibilityChangeReporter. Google's documented shape is:
D CompatibilityChangeReporter: Compat change id reported: 194833441; UID 10265; state: ENABLED
That is the format from the documentation, not output captured from a device during this article. The change ID tells you which behaviour is in play, the UID identifies the app — useful when several builds are installed — and the state tells you whether the change is active.
Important: each change is logged at most once per process. If you toggle a change and see nothing, force stop the app and start it again — the message you are looking for may already have been written and suppressed.
To cut the noise, use Logcat's documented tag filter syntax with *:S as an allowlist, so only the reporter tag is shown:
adb logcat CompatibilityChangeReporter:D *:S
In Android Studio's Logcat window the same idea works through the query field: filter on tag:CompatibilityChangeReporter, and add package:mine to keep only your own app's entries.
Android 16 Changes Worth Testing
Not every Android 16 change applies to every app. The four below most often produce a visible defect in an app that was working the day before. The summary table then adds JobScheduler quota and standby-bucket behaviour, which matters only if your app schedules background work.
Edge-to-edge enforcement
For apps targeting 36, the opt-out attribute R.attr#windowOptOutEdgeToEdgeEnforcement is deprecated and disabled, so the app cannot avoid drawing edge to edge. Note the version split Google documents: targeting 36 on an Android 15 device, the attribute still works; on an Android 16 device it does not. That difference alone can make a bug appear on one test device and not another.
What to inspect: content under the status or navigation bar, a scrollable view whose last item is unreachable, toolbars without insets, bottom buttons behind the gesture bar, and dialogs that apply insets twice.
Predictive back
When an app targets 36 and runs on Android 16, the predictive back animations — back-to-home, cross-task and cross-activity — are enabled by default. Google states that onBackPressed is not called and KeyEvent.KEYCODE_BACK is no longer dispatched. The documented options are to update to the supported back navigation APIs, or to set android:enableOnBackInvokedCallback to false on the <application> or <activity> as a temporary opt-out.
What to test: back from a nested screen, back with a dialog open, back after an unsaved edit, and back twice quickly. The symptom is rarely a crash — usually a screen that no longer exits, or a confirmation prompt that never appears.
Large-screen adaptive layouts
For apps targeting 36, orientation, resizability and aspect ratio restrictions no longer apply on displays with smallest width of 600dp or more: the app fills the window regardless of the orientation it requested, and pillarboxing is not used. Fixed-portrait layouts are the ones that break.
Google documents exceptions and a temporary opt-out — the android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY property at activity or application level — with a warning that it will not apply when targeting API 37. Treat it as time bought, not a fix.
Background scheduling behaviour
The fixed-rate change is narrower than headlines suggest. Before targeting 36, when scheduleAtFixedRate missed executions because the app was outside a valid process lifecycle, all of them ran when it returned. When targeting 36, at most one missed execution runs. Google also documents JobScheduler quota adjustments that apply to all apps on Android 16, with stop reasons available through WorkInfo.getStopReason() or JobParameters.getStopReason().
What to test: a job that should fire while the app is backgrounded, then whether it fires once or in a burst on return. If your app does not use these APIs, none of this applies to it.
| Android 16 change | Possible symptom | What to test | Debugging direction |
|---|---|---|---|
| Edge-to-edge enforced | Text under the status bar; button behind the gesture bar | Every screen, light and dark, portrait and landscape | Inspect inset handling; compare a target-35 and target-36 build on the same device |
| Predictive back default | Back does nothing, or exits the app from a nested screen | Nested flows, open dialogs, unsaved changes, rapid double back | Search the codebase for intercepted back callbacks; verify the animation actually previews the destination |
| Large-screen restrictions ignored | Stretched columns, clipped CTAs, letterboxed UI | Tablet or foldable at sw600dp+, rotation, split screen, resize | Toggle UNIVERSAL_RESIZABLE_BY_DEFAULT to reproduce without changing the target |
| Fixed-rate scheduling | Missed periods no longer replayed in a burst; counts differ | Background a task, return after several missed periods, compare tallies | Toggle STPE_SKIP_MULTIPLE_MISSED_PERIODIC_TASKS; log a counter rather than guessing |
| JobScheduler quota and standby buckets | Jobs deferred or stopped sooner than before | Work that runs while the app is not visible; long downloads | Read the stop reason; use the quota overrides and standby-bucket commands |
A Practical Android 16 Compatibility Testing Workflow
This is a recommended sequence, not a Google certification process. It is ordered so the cheapest evidence arrives first.
- Write down the Android versions you support and the current
targetSdkVersionof the build you are testing. - Produce a debuggable build of that exact commit, so results are reproducible.
- Install it on an Android 16 emulator or physical device and run your normal smoke tests.
- Review the all-apps changes for that release and test each against the features you ship.
- Review the changes gated by API 36 and list the ones your code could plausibly hit.
- Enable those changes one at a time and reproduce the affected flow after each.
- When something fails, note the change ID, then disable that change and retest to confirm causation.
- Read Logcat with the reporter tag filtered, after a force stop, to see which changes were actually reached.
- Fix the underlying behaviour rather than pinning the toggle.
- Reset the override and retest with the change at its default state.
- Repeat your regression suite on at least one older supported API level.
- Only then raise
targetSdkVersionto 36, and repeat the smoke tests against the new default set. - Build the release AAB and confirm the merged manifest carries the intended target.
- Record the results in a report that names devices, API levels and change IDs.
Android 16 Compatibility Testing Matrix
A starting checklist for a build that targets 36. It is a recommended matrix for your own regression pass, not an official Google test suite.
| Test scenario | Expected result | What to inspect |
|---|---|---|
| Fresh installation | Installs and launches to a usable state | First-run flow, permission prompts, empty states, no crash on cold start |
| Upgrade from the previous version | Data and session survive; no migration error | Stored preferences, database schema, logged-in state after upgrade |
| Login and registration | Authentication completes through every path | Token refresh, deep-linked sign-in, redirect back into the app |
| Main navigation | Every destination reachable, state preserved | Tab restore, back stack depth, argument passing |
| Back navigation | Exits the screen as designed, nothing swallowed | Predictive animation target, dialogs, unsaved-change guards |
| Edge-to-edge UI | All content visible and reachable | Top and bottom insets, last list item, bottom action bars, keyboard overlap |
| Notifications | Delivered, shown and opened correctly | Channel settings, tapping through, foreground-service notification |
| Runtime permissions | Grant, deny and revoke all leave a usable app | Denial copy, settings round-trip, partial access |
| Background work | Runs within the documented constraints | Job stop reasons, missed-period behaviour, standby bucket |
| Network requests | Same results as the previous platform | Timeouts, retries, certificate handling, offline states |
| Tablet and foldable layouts | Layout adapts without clipping | sw600dp+ window sizes, rotation, unfold, multi-window resize |
| Activity recreation | UI state restored, no duplicate work | Saved state, re-fired network calls, duplicated observers |
| Third-party SDKs | No new warnings, crashes or silent failures | SDK versions updated for API 36, their background work, their permission requests |
| Startup and crash behaviour | Cold and warm start succeed, no new crash cluster | Startup time, ANRs, the pre-launch report for the uploaded build |
Common Compatibility Testing Problems
| Symptom | Possible cause | Diagnostic and action |
|---|---|---|
| App is missing from App Compatibility Changes | No debuggable build installed, or a different package is installed | Check the build type and the applicationId; uninstall the release build first |
| Toggle exists but cannot be switched | The change is not toggleable on a public user build, or is logged only | Compare against the restriction table; use a preview or beta device build where a category is not toggleable |
No device listed by adb devices |
USB debugging off, unaccepted RSA prompt, or a stale server | Enable USB debugging, accept the prompt on the device, restart the adb server |
| The change has no visible effect | The code path was never reached, or the change does not affect that feature | Force stop, relaunch, read the reporter output; confirm the app actually calls the affected API |
| Behaviour differs between emulator and physical device | Device build type, OEM customisation, hardware features, GPU differences | Record the build type and model in the report; treat the physical device as the tie-breaker |
| Logcat shows no reporter lines | Change already logged in this process, wrong filter, or wrong process selected | Force stop the app, filter on the tag with *:S, confirm you are looking at your package's UID |
| Issue appears only after the target changes | Several gated changes became active at once | Go back to the old target, force the gated changes on one by one, and find the single culprit |
| Third-party SDK still misbehaves | The SDK pins its own target, or uses an API affected by an all-apps change | Check the SDK's release notes for Android 16 support, and test with the reporter tag to see which IDs its code reaches |
Testing Android 16 on Emulator vs Physical Devices
| Environment | Strengths | Limitations | Best use |
|---|---|---|---|
| Android Emulator | Many API levels and screen sizes on one machine; quick resets; easy to script with ADB | No real camera or cellular stack; different performance; some OEM behaviour absent | First pass on behaviour changes; layout checks across many window sizes |
| Physical phone | Real insets, real gesture navigation, real power management and thermals | One model at a time; build type limits which toggles exist | Confirming defects and verifying fixes; gesture and inset behaviour |
| Tablet or foldable | The only way to see sw600dp+ behaviour, resize and fold transitions | Less common hardware; emulator configurations are good but not identical | Large-screen layout work and the resizability changes |
| Second manufacturer | Catches OEM settings screens, skins and battery managers that differ | Costs time and device access | Before release, on the flows your analytics say matter most |
Use both where the project allows it. Emulators make breadth cheap, physical devices make results believable, and a defect that only reproduces on hardware is exactly the kind that reaches production. Our real device testing guide covers how to pick the matrix rather than guessing.
How to Write a Useful Compatibility Test Report
A compatibility defect is only actionable if the next person can recreate the state you were in, which means recording the environment, the toggle state and the evidence together.
| Field | What to record |
|---|---|
| Build identity | App name, version name and version code, package name, build type (debug or release), targetSdkVersion |
| Environment | Device model and manufacturer, Android version and API level, whether it is a preview, beta or public build, emulator or physical |
| Change under test | Change name and ID, its default state, and the override applied (enabled, disabled or none) |
| Reproduction | Numbered steps from app launch, including orientation, window size and account state |
| Expected vs actual | One sentence each, describing behaviour rather than opinion |
| Frequency | How many runs out of how many, and whether it reproduces after a clean install |
| Evidence | Reporter log lines, other relevant Logcat, screenshot or screen recording |
| Triage | Severity, suspected cause, suggested fix, and retest status after the fix |
Frequently Asked Questions
What is the Android compatibility framework?
The platform mechanism that decides per app whether a behaviour change is active, driven from Developer Options or ADB. It lets you test one change at a time instead of meeting all of them at once.
Can I test Android 16 behaviour without changing targetSdkVersion?
Yes for basic testing. Google states the platform adapts its own logic, so you can force individual gated changes on without editing the manifest or recompiling.
Do compatibility toggles require a debug build?
Yes. A debuggable build variant is required; release builds do not expose the toggles, and ADB overrides do not work on them either.
How do I open App Compatibility Changes?
On Android 16: Settings › System › Advanced › Developer options › App Compatibility Changes, then select your app. Manufacturer builds sometimes place it slightly differently.
Can ADB control Android compatibility changes?
Yes. am compat enable, am compat disable and am compat reset take a change ID or name plus a package name, and dumpsys platform_compat lists the device's changes.
What is a compatibility change ID?
A unique numeric identifier for one behaviour change. The same ID appears in the toggle list, in dumpsys output, in Logcat and in ADB commands, so one issue can be followed across all four.
How do I find compatibility issues in Logcat?
Filter on the CompatibilityChangeReporter tag — for example adb logcat CompatibilityChangeReporter:D *:S — and force stop the app first, because each change is reported at most once per process.
Should I test on both emulator and real devices?
Yes where possible. The emulator gives breadth across API levels and window sizes; a physical device gives real insets, gesture navigation and power behaviour, and is the better tie-breaker.
Does Android 16 compatibility testing guarantee Google Play approval?
No. Testing reduces defects; it does not guarantee review outcomes, rankings or uninterrupted distribution.
What is the difference between Android compatibility testing and Google Play target API compliance?
Compliance is whether the uploaded artifact declares the required target level. Compatibility testing is whether the app still behaves correctly on that platform. You need both; passing one says nothing about the other.
Official Android Resources
- Compatibility framework tools — toggling changes in Developer Options and ADB, restrictions, and the reporter states
- Android 16 compatibility framework changes — every toggleable change with its ID and default state
- Migrate apps to Android 16 — the migration path and toolchain steps
- Android 16 behaviour changes for apps targeting API 36 — edge-to-edge, predictive back, large screens, scheduling
- Android 16 behaviour changes affecting all apps — JobScheduler quotas, broadcast priorities and more
- Android Emulator — creating and running Android 16 virtual devices
Conclusion
Compatibility testing works best when you stop treating it as a single pass against a new Android version. Two questions run in parallel: what does the platform do to every app, and what does it do to apps declaring this target. The framework separates them; the reporter tells you which one you just hit.
The habit worth building is narrow: review the documented changes, pick the ones your code could plausibly reach, force them one at a time, and keep the change ID in every note. When something breaks, the ID is the difference between a guess and a diagnosis — and between fixing the app and pinning a toggle you will have to unpin later.
If your next step is a closed test on real hardware, the same discipline applies: name the device, the API level and the change under test, then let testers confirm it. TestersHQ works with Android teams on that last mile, where a compatibility report from a real device is worth more than a clean run on one emulator.