← All posts

APP UPDATES & COMPATIBILITY

Android 16 Compatibility Testing: ADB, Compatibility Toggles & Debugging

Android 16 Compatibility Testing: ADB, Compatibility Toggles & Debugging

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
Compatibility framework workflow Isolate one behaviour change, reproduce, read the log, fix, retest STEP 1 Debug build debuggable variant, installed on API 36 STEP 2 Open the toggle App Compatibility Changes for the app STEP 3 Enable one change on by default, or force one off STEP 4 Reproduce the affected flow, note what changed STEP 5 Logcat / ADB change ID, UID and ENABLED / LOGGED STEP 6 Fix the cause not the symptom; keep the change ID STEP 7 Retest with the change on and off, then reset ALWAYS to reset the override before the next test, so results stay valid Requires a debuggable build. The platform adapts its own logic, so basic toggle testing needs no targetSdkVersion change and no recompile.
Figure 1 — The compatibility framework loop. Each step is a separate, recordable observation, which is what makes a change ID enough to work from.

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:

  1. Build and install a debuggable variant of your app on the Android 16 device or emulator.
  2. If Developer Options are not already on, enable them.
  3. Open Settings and navigate to System › Advanced › Developer options › App Compatibility Changes.
  4. Select your app from the list.
  5. 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.
  6. Find the one change you intend to test and use its switch.
  7. The app is killed when you toggle, so relaunch it deliberately rather than being surprised.
  8. 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.

Testing one change at a time Select the app, pick one change, test, record, then reset before the next run 1 Select app Pick the package from the list on the toggle screen 2 Change ID Read the name and description in the official list 3 Enable it Toggle, or use am compat enable app is killed 4 Test feature Relaunch, run the same steps as before 5 Record Pass or fail, with the ID and log excerpt 6 Reset am compat reset, then next change On a public release build, only debuggable apps can toggle changes gated by targetSDKVersion. Changes that affect all apps appear in the list but are not toggleable on a public user build.
Figure 2 — Testing one change at a time, and resetting before the next run so results stay attributable.

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.

  1. Write down the Android versions you support and the current targetSdkVersion of the build you are testing.
  2. Produce a debuggable build of that exact commit, so results are reproducible.
  3. Install it on an Android 16 emulator or physical device and run your normal smoke tests.
  4. Review the all-apps changes for that release and test each against the features you ship.
  5. Review the changes gated by API 36 and list the ones your code could plausibly hit.
  6. Enable those changes one at a time and reproduce the affected flow after each.
  7. When something fails, note the change ID, then disable that change and retest to confirm causation.
  8. Read Logcat with the reporter tag filtered, after a force stop, to see which changes were actually reached.
  9. Fix the underlying behaviour rather than pinning the toggle.
  10. Reset the override and retest with the change at its default state.
  11. Repeat your regression suite on at least one older supported API level.
  12. Only then raise targetSdkVersion to 36, and repeat the smoke tests against the new default set.
  13. Build the release AAB and confirm the merged manifest carries the intended target.
  14. 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 installationInstalls and launches to a usable stateFirst-run flow, permission prompts, empty states, no crash on cold start
Upgrade from the previous versionData and session survive; no migration errorStored preferences, database schema, logged-in state after upgrade
Login and registrationAuthentication completes through every pathToken refresh, deep-linked sign-in, redirect back into the app
Main navigationEvery destination reachable, state preservedTab restore, back stack depth, argument passing
Back navigationExits the screen as designed, nothing swallowedPredictive animation target, dialogs, unsaved-change guards
Edge-to-edge UIAll content visible and reachableTop and bottom insets, last list item, bottom action bars, keyboard overlap
NotificationsDelivered, shown and opened correctlyChannel settings, tapping through, foreground-service notification
Runtime permissionsGrant, deny and revoke all leave a usable appDenial copy, settings round-trip, partial access
Background workRuns within the documented constraintsJob stop reasons, missed-period behaviour, standby bucket
Network requestsSame results as the previous platformTimeouts, retries, certificate handling, offline states
Tablet and foldable layoutsLayout adapts without clippingsw600dp+ window sizes, rotation, unfold, multi-window resize
Activity recreationUI state restored, no duplicate workSaved state, re-fired network calls, duplicated observers
Third-party SDKsNo new warnings, crashes or silent failuresSDK versions updated for API 36, their background work, their permission requests
Startup and crash behaviourCold and warm start succeed, no new crash clusterStartup time, ANRs, the pre-launch report for the uploaded build
Android 16 compatibility test areas Seven areas to cover per build, each with its own failure signature UI & insets content under the status and nav bars after edge-to-edge Navigation back gestures, predictive animations, dialog dismissal Permissions grant, deny, revoke, one-time and limited access paths Background work jobs, quotas, fixed- rate tasks, standby bucket behaviour Devices & windows tablets, foldables, split screen, resize and rotation on sw600dp+ displays; activity recreation Third-party SDKs SDKs that read platform APIs, pool threads or schedule work on your behalf Regression core flows on older supported API levels, plus startup and crash rates on a new build A recommended coverage map, not a Google certification checklist. Weight the areas your app actually depends on.
Figure 3 — The same coverage in one view, for planning a regression pass across UI, navigation, permissions, background work, devices, SDKs and older API levels.

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 identityApp name, version name and version code, package name, build type (debug or release), targetSdkVersion
EnvironmentDevice model and manufacturer, Android version and API level, whether it is a preview, beta or public build, emulator or physical
Change under testChange name and ID, its default state, and the override applied (enabled, disabled or none)
ReproductionNumbered steps from app launch, including orientation, window size and account state
Expected vs actualOne sentence each, describing behaviour rather than opinion
FrequencyHow many runs out of how many, and whether it reproduces after a clean install
EvidenceReporter log lines, other relevant Logcat, screenshot or screen recording
TriageSeverity, suspected cause, suggested fix, and retest status after the fix
Compatibility test report The fields that let another developer reproduce the defect without asking you anything ENVIRONMENT Device modelPixel 9 Android version16 API level36 Build typedebuggable App version2.4.0 (240) Target SDK35 / 36 Change ID357141415 OBSERVATION Test steps 1. Rotate to landscape 2. Open the detail screen 3. Background and return Expected Layout refits the window Actual Right edge clipped, CTA off-screen Frequency3 of 3 runs EVIDENCE & VERDICT Logcat excerpt CompatChangeReporter id 357141415 state ENABLED Screenshot / videoattached Severitymajor Suggested fixuse insets Retest statusfixed, verified Example layout only. Values are illustrative, not captured from a real device or a Google interface.
Figure 4 — The report fields that let another developer reproduce the defect: environment, change ID, expected versus actual, and evidence.

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

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.

Ready for your 12 testers?

Stop reading — start testing. Real people on real devices, 14 days, zero chasing.

WhatsApp