Testing Android Apps Across OS Versions: Complete Guide
Android OS versions aren't backward-compatible—not entirely. Code that works on Android 13 might fail on Android 12. A feature that passes on Android 14 might crash on Android 11.
Each Android version introduces breaking changes: new permission models, deprecated APIs, changed behaviors, new features. Testing across OS versions is how you ensure your app works for all users, regardless of which version they're running.
The challenge: if you support Android 10 through Android 15, you could test on 6 different OS versions. Multiply that by device variations, and testing scope explodes. This guide explains how to test across OS versions strategically—catching compatibility issues without testing exhaustively.
Understanding Android OS Version Changes
Major Breaking Changes by Version:
Android 10 (2019):
- Scoped storage (limited file access)
- Background execution restrictions
- Gesture navigation (on-screen buttons removed)
- Package visibility changes
Android 11 (2020):
- Hibernation of unused apps
- More permission restrictions
- Microphone/camera indicator system
- One-time permissions
Android 12 (2021):
- Approximate location permission
- Bluetooth runtime permission
- Clipboard access notification
- Material You design system
Android 13 (2022):
- Per-app language preferences
- Stricter enforce of
@hideAPIs - Bluetooth scan permission
- Credential management system
Android 14 (2023):
- Partial media permissions
- Grammatical gender inflection
- Data safety requirement on Play Store
- Restricted access to device identifiers
Android 15 (2024):
- UMP (User Messaging Platform) required
- Restrictions on implicit intents
- Expanded SELinux restrictions
- Enhanced security for WebViews
Key Areas to Test Across OS Versions
API Level Compatibility:
- APIs available at each Android version
- APIs marked deprecated (work but will be removed)
- APIs removed in new versions
Test across your minimum supported API level and target API level.
Permissions Model:
- Android 6+: Runtime permissions (ask at runtime, not install time)
- Android 10+: Scoped storage
- Android 11+: Microphone/camera indicators
- Android 12+: Bluetooth runtime permission
- Android 13+: Per-app language
Permissions handling varies significantly. Test every permission request on supported Android versions.
UI & Navigation:
- Android 10+: Gesture navigation (replaces navigation buttons)
- Screen sizes and aspect ratios vary by Android version
- Material Design evolution (colors, typography)
Test UI across Android versions to catch layout/navigation issues.
Background Execution:
- Android 8+: Background execution restrictions (can't run code in background indefinitely)
- Android 10+: Location access restrictions
- Android 12+: Foreground service restrictions
If your app uses background services, test extensively across versions.
Storage Access:
- Pre-Android 10: Broad file access
- Android 10+: Scoped storage (only access app-specific directories)
- Android 11+: MANAGE_EXTERNAL_STORAGE permission deprecated
File access code works very differently across versions. Test thoroughly.
Network & Connectivity:
- Cleartext traffic blocking (HTTPS required on Android 9+)
- DNS over HTTPS support
- Network security policies changed
Test network calls across versions.
Setting Up OS Version Testing
Android Emulator – Multiple OS Versions:
Create separate emulator instances for each Android version:
1. Open Android Studio
2. Tools → Device Manager
3. Create new device for each OS version (Android 10, 11, 12, 13, 14, 15)
4. Launch each separately as needed
Each emulator is isolated, letting you test version-specific behavior.
Physical Devices with Different OS Versions:
If using real devices, maintain devices with different OS versions:
- One device on minimum API level (catch legacy issues)
- One device on target API level (latest features)
- One device on Android 12–13 (middle ground)
Firebase Test Lab – Multi-Version Testing:
Run automated tests across multiple OS versions in Firebase Test Lab:
1. Configure test matrix with OS versions
2. Run test suite
3. Tests execute on all OS versions automatically
4. Reports show failures by OS version
Cloud Device Labs:
Services like BrowserStack provide real devices across OS versions. Useful for manual testing.
Common OS Version Compatibility Issues
API Level Mismatches:
// WRONG: API 30+ only, will crash on Android 9
val response = MediaStore.getDocumentUri(...)
// CORRECT: Check API level first
val response = if (Build.VERSION.SDK_INT >= 30) {
MediaStore.getDocumentUri(...)
} else {
oldMethod()
}
Fix: Check API level before using new APIs. Use AndroidX compatibility libraries.
Permission Denials:
// WRONG: Assumes permission is granted
val location = LocationManager.requestLocationUpdates(...)
// CORRECT: Check permission first (Android 6+)
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED) {
LocationManager.requestLocationUpdates(...)
} else {
ActivityCompat.requestPermissions(...)
}
Fix: Always check permissions at runtime. Handle denials gracefully.
Scoped Storage Issues (Android 10+):
// WRONG: Direct file path access (won't work Android 10+)
val file = File("/storage/emulated/0/DCIM/photo.jpg")
// CORRECT: Use MediaStore or app-specific directory
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
val cursor = contentResolver.query(collection, ...)
Fix: Use MediaStore for shared files. Use app-specific directories for app data.
Deprecated Warnings:
When targeting new Android versions, you'll see deprecation warnings in Android Studio. Fix them before the deprecated API is removed.
Background Execution Failures (Android 8+):
// WRONG: Long-running background thread
thread {
// This might be killed by Android
while (true) {
doWork()
}
}
// CORRECT: Use foreground service or WorkManager
startForegroundService(Intent(this, MyService::class.java))
// OR
WorkManager.getInstance().enqueueUniquePeriodicWork(...)
Fix: Use appropriate background APIs for your use case (foreground services, WorkManager, etc.).
Testing Workflow for OS Versions
Step 1: Identify Supported OS Range
- Minimum supported: Android 10 (or 11, 12 based on business decision)
- Target: Latest Android version
- Document this clearly
Step 2: Build Test Matrix
Android 10 (minimum)
Android 12
Android 14 (target)
Test at least 3 versions (min, mid, target). If supporting 6+ versions, test more versions.
Step 3: Set Up Testing Environment
Create emulators (or get devices) for each version. Keep list of devices.
Step 4: Automated Testing Across Versions
- Write tests covering critical features
- Run tests on all OS versions
- Capture results by version
Step 5: Manual QA Testing
QA team tests critical features on real devices (especially minimum and target versions).
Step 6: Target Latest Android Version
Always target the latest Android version in your build.gradle:
android {
compileSdk = 35 // Latest Android API
targetSdkVersion = 35
minSdkVersion = 24 // Minimum supported
}
Step 7: Monitor Post-Release
After release, use Play Console to monitor crashes by Android version. This reveals version-specific issues.
Handling Version-Specific Code
When OS versions have different behavior, use version checks:
Version Check Pattern:
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
// Android 13+ code
useNewApi()
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
// Android 12 code
useCompatibleApi()
}
else -> {
// Android < 12
useLegacyApi()
}
}
Using AndroidX Compatibility Libraries:
AndroidX provides compatibility wrappers for APIs across versions:
// Works across Android versions
ContextCompat.checkSelfPermission(context, permission)
ContextCompat.startActivity(context, intent, options)
NotificationCompat.Builder(context).setContentTitle("...").build()
Prefer AndroidX libraries over version checking when possible (cleaner code).
Testing Strategy by Feature Type
Files & Storage:
- Test file write/read on Android 10+ (scoped storage changes)
- Test directory access across versions
- Test permission flows on Android 10+
Permissions:
- Test runtime permission requests on Android 6+
- Test permission denials
- Test permission changes between OS versions
Background Tasks:
- Test on Android 8+ (background restrictions)
- Test foreground services
- Test WorkManager
Notifications:
- Test notification channels (Android 8+)
- Test notification permissions (Android 13+)
- Test notification appearance across versions
Location:
- Test location permission (varies by version)
- Test location access in background
- Test approximate location (Android 12+)
Connectivity:
- Test HTTPS enforcement (Android 9+)
- Test network security policies
- Test WiFi/cellular switching
Using Build Variants for Version-Specific Features
If your app needs drastically different implementations for different OS versions, use build variants:
flavorDimensions = ["osVersion"]
productFlavors {
minSupported {
targetSdk 34
}
latest {
targetSdk 35
}
}
This lets you build separate APKs optimized for different OS versions (advanced technique).
Testing Tools That Support Multiple Versions
Android Studio Emulator: Create multiple emulator instances for different OS versions.
Firebase Test Lab: Specify OS versions in test matrix, run tests automatically.
Espresso with Parameterized Tests: Run same tests across multiple OS versions.
@RunWith(Parameterized::class)
class OsVersionTests(private val osVersion: Int) {
// Tests run on multiple OS versions
}
Device Simulation Platforms: Like DevicesChanger, let you define testing scenarios targeting specific OS versions, simplifying multi-version testing.
Conclusion
Testing across OS versions ensures your app works for all users, regardless of which Android version they're running. The key is smart sampling: test your minimum supported version (catch legacy issues), target version (leverage new features), and 1–2 middle versions.
Use emulators for rapid testing, cloud device labs for broader coverage, and real devices for high-impact features. Monitor crash data post-release to continuously improve version compatibility.
Teams that master OS version testing reduce version-specific bugs, support legacy Android versions confidently, and adopt new features quickly.
Related pages
How Device Changer fits
Structured device simulation and device profiles help teams run the workflows above with reproducible Android contexts—aligned with QA testing and mobile testing practice.
Interface screenshots
FAQ
How does this relate to physical device labs?
Use simulation and profiles to scale coverage; validate critical builds on real hardware before release.
Where should automation sit in the pipeline?
Automate stable checks early (CI); keep exploratory and edge scenarios in dedicated QA passes.