CLOUSD
Open account
Testing

Test an app on different Android versions

Version by version, what changes for your app from Android 12 to 17, how to try target-gated behavior before you raise targetSdkVersion, and a checklist for every release.

Android ships a major release every year, and each one changes behavior your app depends on. Testing an app on different Android versions is not about installing the same APK on six phones and checking that it opens. It is about knowing which changes reach you on which release, and why the same build can behave differently on two of them.

This guide maps Android 12 to 17 for app developers: what changes, how to test each change with adb, and how to try target-gated behavior before you raise targetSdkVersion.

Two axes, not one

Every behavior change in Android belongs to one of two groups. Changes for all apps apply as soon as the device runs the new release, whatever your app targets. Target-gated changes apply only when your targetSdkVersion is at least that release's API level.

That gives you two axes. The device version decides the first group, your target decides the second. The notification permission in Android 13 shows why both matter. An app targeting API 32 or lower gets the prompt from the system once it has a notification channel and starts an activity. An app targeting 33 has to request the permission itself, at a moment it chooses. Same code, two different first-run flows.

So the matrix is the versions your users run, times your current target, plus one column for the target you are about to move to.

Android 12 to 17 at a glance

VersionAPI levelChanges that most often break apps
1231 (12L is 32)explicit android:exported; PendingIntent mutability flag; no foreground service starts from the background; approximate location; system splash screen
1333POST_NOTIFICATIONS permission; granular media permissions replace storage reads; photo picker; per-app language
1434"Select photos" partial access; foreground service types required; exact alarms off by default for new installs; export flag for runtime receivers; targets below 23 cannot install
1535edge-to-edge enforced; devices with 16 KB memory pages; targets below 24 cannot install; predictive back animations for opted-in apps
1636edge-to-edge opt-out ignored; predictive back on by default, onBackPressed() no longer called; orientation and resizability limits ignored on large screens
1737Google has said the temporary large-screen opt-out from 16 ends with the next release; read the full behavior changes list before you raise your target

Notifications (Android 13)

On 13 and later, notifications are off until the user allows them. Test three paths: allow, deny, and deny twice, after which the system stops showing the dialog and your app has to send the user to settings. Then test on 12, where there is no prompt, so onboarding does not ask for a permission that does not exist there.

Switch the grant between attempts:

adb shell pm revoke com.example.app android.permission.POST_NOTIFICATIONS
adb shell pm grant com.example.app android.permission.POST_NOTIFICATIONS
adb shell dumpsys package com.example.app | grep POST_NOTIFICATIONS

Revoking does not bring back the first-run dialog after a user has denied twice. To see the flow a new user sees, uninstall and install again.

Photos and media (Android 13 and 14)

Apps targeting 33 can no longer read media with READ_EXTERNAL_STORAGE. They ask for READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READ_MEDIA_AUDIO instead. Android 14 adds a third answer to the photo and video prompt: the user can share only the items they select. The app then holds READ_MEDIA_VISUAL_USER_SELECTED, sees only those files, and should offer a way to change the selection rather than assume it has the whole gallery.

The cleaner route is the photo picker, which needs no permission at all. ActivityResultContracts.PickVisualMedia opens the system picker where it exists and falls back to ACTION_OPEN_DOCUMENT where it does not. On Android 11 and 12 the picker arrives through Google Play system updates, so a given phone may have either path. Log isPhotoPickerAvailable() on each phone and make sure the flow works both ways.

Seed the gallery before you test, because an empty picker hides layout bugs:

adb push sample-photos/ /sdcard/Pictures/

Files pushed into shared storage usually appear in MediaStore within seconds. If they do not, reopen the picker.

Background work (Android 12, 14 and 15)

Three changes move background bugs between versions. From 12, an app in the background can no longer start a foreground service outside a short list of exemptions; the call throws ForegroundServiceStartNotAllowedException. From 14, an app targeting 34 must declare a foregroundServiceType for every foreground service, plus the matching permission such as FOREGROUND_SERVICE_DATA_SYNC, or the start fails. From 15, a dataSync foreground service in an app targeting 35 gets about six hours in a day, then a timeout callback.

Exact alarms changed too. SCHEDULE_EXACT_ALARM appeared in 12, and on 14 it is no longer granted by default to newly installed apps that target 13 or later. Call canScheduleExactAlarms() and handle false.

Force the conditions instead of waiting for them. The first three lines put the phone into doze and your app into a low standby bucket; run the scenario, then restore with the last two:

adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
adb shell am set-standby-bucket com.example.app rare
adb shell dumpsys deviceidle unforce
adb shell dumpsys battery reset

Edge-to-edge and back navigation (Android 15 and 16)

An app targeting 35 draws behind the status and navigation bars on Android 15, whether it asked to or not. Missing insets put toolbars under the status bar, buttons under the gesture bar and text under a cutout. Android 15 still honors an opt-out attribute. On 16, apps targeting 36 lose it.

Test every screen with both navigation modes, because three-button navigation leaves a taller bar to pad for. On AOSP-based builds you can switch from adb; elsewhere, use Settings, System, Gestures:

adb shell cmd overlay enable-exclusive --category com.android.internal.systemui.navbar.threebutton
adb shell cmd overlay enable-exclusive --category com.android.internal.systemui.navbar.gestural

Predictive back took a similar path. On 13 and 14 it sits behind a developer option. On 15 the system animations run for apps that opted in with android:enableOnBackInvokedCallback. On 16, apps targeting 36 get it by default and onBackPressed() is no longer called. Search the code base before you raise the target:

grep -rn "onBackPressed\|KEYCODE_BACK" app/src/main

Move every hit to OnBackPressedCallback, then test gesture back from each screen, including dialogs and bottom sheets.

Android 16 also changes large screens. On displays with a smallest width of 600 dp or more, an app targeting 36 loses its orientation lock and resizeableActivity="false". Android 16 offers a temporary opt-out, and Google has said it ends with the next release, so plan for it to be gone once you target 37. If your app locks portrait, test it on a large screen before you raise the target.

16 KB memory pages (Android 15)

Android 15 added support for devices that use 16 KB memory pages instead of 4 KB. Apps with native code must align their libraries for it, and since November 2025 Google Play has required 16 KB support for new apps and updates that target Android 15 and later. Check a phone's page size and your build:

adb shell getconf PAGE_SIZE
zipalign -c -P 16 -v 4 app-release.apk
llvm-objdump -p lib/arm64-v8a/libfoo.so | grep LOAD

The LOAD segments should show align 2**14. Android Gradle Plugin 8.5.1 and later align uncompressed libraries inside the APK, and NDK r28 and later compile 16 KB aligned by default, so prebuilt third-party libraries are the usual offenders. A phone that reports 4096 tells you nothing about 16 KB behavior; run that check on a 16 KB emulator system image.

Try target-gated changes before raising targetSdk

You do not have to raise targetSdkVersion to see most target-gated behavior. Android's compatibility framework lets you switch individual changes on for a debuggable build:

adb shell dumpsys platform_compat | grep -i edge
adb shell am compat enable CHANGE_NAME com.example.app
adb shell am compat reset CHANGE_NAME com.example.app

The same switches live in Developer options under App Compatibility Changes. Turn on one change at a time and run your smoke pass. When the list is clean, raise the target in a branch and run the full matrix.

Where to run the matrix

Most teams use three kinds of device. Emulator system images exist for every API level and cost nothing, which makes them right for instrumentation tests and API sweeps in CI. Cloud phones give you each release on real phone models and keep state between sessions: Clousd runs Android 12 to 17 on 150+ models, and a phone resets to a fresh state between runs. Physical phones stay the reference for camera, sensors, graphics and vendor behavior.

The model list shows which models run in the cloud and the app testing page shows how to wire the matrix into CI. New to remote phones? Start with testing an Android app online, and see the cloud Android emulator explainer for emulator images versus real models.

A checklist for every release

  • Install the current build on the new release with your current target: it installs, starts and survives process death.
  • Read the "behavior changes for all apps" page and test every item that touches your features.
  • First run with nothing granted, then each permission denied in turn.
  • Notifications: prompt timing, denial, and the link to settings.
  • Media: photo picker, partial access, an empty gallery.
  • Background: foreground services, exact alarms, doze and standby buckets.
  • Layout: insets in both navigation modes, display cutouts, font scale 1.3, dark mode.
  • Back: gesture back from every screen, dialog and bottom sheet.
  • Native code: 16 KB alignment of every library.
  • Raise the target in a branch, read "behavior changes for apps targeting" the new level, and repeat.

Keep reading

All guides

Ready to launch
your first cloud phone?

Try free for 30 minutes

No card needed