How to Test Permission Dialogs on Android (Complete Guide)
Permission dialogs are the gatekeepers between an app’s functionality and the device’s protected resources. When a dialog fails—whether it never appears, is dismissed incorrectly, or blocks the user f
Why Permission Dialogs Matter on Android
Permission dialogs are the gatekeepers between an app’s functionality and the device’s protected resources. When a dialog fails—whether it never appears, is dismissed incorrectly, or blocks the user from proceeding—core features break, user trust erodes, and the app may be flagged by Play Store reviewers for misleading behavior. In production, a missing permission can cause silent crashes (e.g., trying to read contacts without READ_CONTACTS), lead to ANRs when the app waits for a result that never arrives, or expose data if the app incorrectly assumes a permission was granted. Conversely, over‑granting permissions (e.g., always allowing location) creates privacy risks and can trigger rejection during the store’s privacy review. Testing these dialogs therefore touches reliability, security, compliance, and user experience—all critical dimensions for any Android release.
Comprehensive Test Matrix for Permission Dialogs
A systematic matrix helps ensure that every dimension of permission handling is exercised. Below is a detailed breakdown that can be copied into a test‑case management tool or used as a checklist for exploratory sessions.
| Category | Sub‑scenario | Expected Outcome | Test Notes |
|---|---|---|---|
| Happy Path | User grants a single runtime permission (e.g., CAMERA) when prompted | Permission is granted, app proceeds to the feature that needs it | Verify that the permission appears in Settings → Apps → [App] → Permissions as granted |
| User grants multiple permissions in one flow (e.g., CAMERA + MICROPHONE) | All requested permissions are granted, feature works | Ensure no intermediate dialog is left dangling | |
| User grants permission via system Settings after initially denying | Permission toggles to granted, app resumes functionality without restart | Simulate by denying, opening Settings, toggling, returning to app | |
| App targets Android 13+ and requests a post‑notification permission | Notification permission dialog appears, granting enables notification channel | Check that notification channel is created and a test notification is delivered | |
| App requests a special access permission (e.g., SYSTEM_ALERT_WINDOW) | Special permission dialog appears, granting allows overlay drawing | Verify overlay can be drawn on top of other apps | |
| Error Paths | User denies a permission permanently (selects “Don’t ask again” and Deny) | Permission remains denied, subsequent requests should show rationale UI (if implemented) or be silently ignored | Confirm that app does not crash and shows appropriate fallback UI |
| User denies but later enables permission via Settings | Permission toggles to granted, app recovers | Test after a process kill to ensure state persists | |
| App requests a permission that is already granted by a previous install (e.g., via pre‑grant) | No dialog appears, app proceeds directly | Use adb shell pm grant to pre‑grant and verify absence of dialog | |
| App requests a permission that is blocked by device policy (e.g., DISALLOW_CAMERA) | System shows a policy‑denied toast, no dialog appears | Use a device owner or managed profile to enforce the restriction | |
| App requests a permission after the user has selected “Never ask again” for a different permission in the same group | The system may suppress the dialog for the whole group; app receives DENIED | Verify behavior for permission groups like LOCATION (ACCESS_FINE_LOCATION vs ACCESS_COARSE_LOCATION) | |
| Edge Cases | Permission request occurs while the app is in the background (e.g., via a foreground service) | System may delay the dialog until the app returns to foreground or show a heads‑up notification | Confirm that the app does not crash and that the service handles the denial gracefully |
| Multiple permission requests triggered in rapid succession (e.g., on app launch) | System queues dialogs; user sees them one after another | Ensure no dialog is skipped and that the app tracks each request’s result | |
| Permission request appears after a configuration change (rotation, multi‑window) | Dialog persists correctly; app does not lose request context | Test with adb shell am broadcast -a android.intent.action.CONFIGURATION_CHANGED | |
| App targets Android 12+ and requests approximate location while the user has granted only coarse location | System shows a separate dialog for precise location if the app upgrades request | Validate that the app handles the upgrade flow | |
App uses the new POST_NOTIFICATIONS permission on Android 13+ and the user has disabled notification channels globally | System shows a dialog with a warning that notifications are disabled globally | Check that the app respects the global setting and does not spam the user | |
| App requests a permission that requires a runtime toggle in OEM‑specific settings (e.g., Xiaomi’s “MIUI optimization”) | Dialog may be suppressed or replaced by OEM UI | Test on at least two OEM skins (e.g., Samsung OneUI, Xiaomi MIUI) | |
| Accessibility | User with TalkBack enabled navigates the permission dialog | TalkBack reads the permission title, description, and buttons correctly; focus order is logical | Verify that the dialog is reachable via swipe gestures and that the “Allow” and “Deny” buttons are announced |
| User with Switch Control selects the dialog via external switch | The switch can highlight and activate each button | Ensure that the dialog does not disappear before the switch action completes | |
| User changes font size to largest or enables bold text | Dialog text scales without clipping; buttons remain tappable | Test with adb shell settings put system font_scale 1.3 and adb shell settings put accessibility bold_text_enabled 1 | |
| User with color blindness relies on icons or text, not color alone | Dialog does not rely solely on color to differentiate allowed vs denied state | Confirm that the UI uses distinct shapes or text labels | |
| Security & Privacy | App attempts to use a permission before the dialog result is received (race condition) | App should either wait for the callback or handle a missing permission gracefully; no crash or data leak | Use adb shell monkey -p to inject rapid events and watch for crashes |
| App logs the raw permission request code or token in Logcat | No sensitive data (e.g., exact GPS coordinates) is leaked in logs | Verify with adb logcat that no permission‑related PII appears | |
| App requests a permission that is not declared in the manifest (mis‑configuration) | System throws SecurityException; app should catch and show a user‑friendly error | Test by removing a permission from manifest while keeping the request code | |
App uses a permission group to request a dangerous permission without declaring the specific one (e.g., requests LOCATION group but only needs coarse) | System grants the whole group; verify that the app does not over‑collect data | Check that the app’s privacy policy matches the granted set | |
App attempts to bypass the dialog via adb shell pm grant in a production build (malicious intent) | The grant succeeds only if the app is debuggable or the device is rooted; production builds should reject | Confirm that a non‑debuggable, non‑rooted device does not allow the grant via adb unless the user explicitly approves via Settings |
Happy Path Scenarios
When testing the happy path, focus on the *exact* moment the system presents the dialog. Use a clean device state (no prior grants) and observe that the dialog’s title matches the permission’s label, the description explains why the app needs it, and the two action buttons are present. After granting, immediately invoke the protected API (e.g., Camera.open()) and assert that no SecurityException is thrown. Additionally, verify that the permission appears as granted in the system UI and that a subsequent request for the same permission does not trigger another dialog (the system remembers the choice).
Error Path Scenarios
Error paths reveal how the app behaves when the user refuses access. A robust app should degrade gracefully: hide the feature, show an inline explanation, or offer a retry button that re‑triggers the request after the user has changed their mind in Settings. Test the “Don’t ask again” flow by denying, checking the checkbox, and then attempting to re‑request the permission; the system should skip the dialog and return PERMISSION_DENIED directly to the callback. Confirm that your app does not treat this as a fatal error but instead presents a UI that guides the user to Settings.
Edge Cases
Edge cases often surface only under specific device states or OS versions. For example, on Android 12 (API 31) the system introduced *approximate location*; if your app requests ACCESS_FINE_LOCATION while the user has only granted coarse, the system shows a separate dialog for the precise location upgrade. Similarly, Android 13’s POST_NOTIFICATIONS permission requires a distinct runtime request even if the app previously sent notifications via notification channels. Test each OS version you support, using emulator system images or real devices, to confirm that the dialog flow matches the platform’s expectations.
Accessibility Considerations
Permission dialogs are system‑provided UI, but they still must be usable by people who rely on assistive technologies. When TalkBack is active, swipe left/right should move focus between the permission explanation, the “Allow” button, and the “Deny” button, with each element announcing its role and state. Verify that the dialog does not trap focus (i.e., the user can swipe out to the underlying app) and that the announcement includes the permission group (e.g., “Location, fine location”). For Switch Control, ensure that a single switch press can highlight each button and that a second press activates it. Finally, test with the largest font size and bold text enabled to confirm that no text is truncated and that touch targets remain at least 48 dp.
Security & Privacy Implications
A permission dialog that is bypassed or mishandled can lead to data leakage or privilege escalation. Ensure that your app never reads protected data before the callback returns PERMISSION_GRANTED. Use strict mode or a custom SecurityException listener to catch any premature access. Additionally, audit your logging: never log the raw grantResults array or any data obtained via the permission (e.g., GPS coordinates) unless you have explicitly masked PII. Finally, confirm that your app’s manifest declares only the permissions it truly needs; excess declarations increase the attack surface and may trigger store warnings.
Manual Testing Approach
Manual testing remains valuable for catching subtle UI glitches, OEM‑specific quirks, and accessibility problems that automated scripts may overlook. Below is a repeatable, step‑by‑step process you can follow on a physical device or emulator.
Preparing the Device/Emulator
- Start with a clean state – Either factory‑reset the emulator or use
adb shell pm clearto remove app data and revoke all runtime permissions. - Install the test build –
adb install -r app-debug.apk. Ensure the build is *debuggable* if you intend to useadb shell pm grantfor pre‑granting; otherwise, keep it non‑debuggable to mimic production. - Enable developer options – On the device, go to Settings → About phone → tap Build number seven times, then toggle USB debugging.
- Grant necessary ADB permissions – On Windows/macOS/Linux, verify
adb devicesshows your device. - Set up accessibility tools – Turn on TalkBack, Switch Control, or font scaling via Settings → Accessibility → Display size to validate the dialog under those conditions.
- Prepare a test script – Have a simple note‑taking app or a test harness that triggers the permission request via a button press. This makes it easy to repeat the flow without navigating through the app each time.
Step‑by‑Step Manual Test Procedure
| Step | Action | Observation | Pass/Fail Criteria |
|---|---|---|---|
| 1 | Launch the app and navigate to the screen that triggers the permission request (e.g., “Take Photo”). | The app shows a button or initiates the request automatically. | UI is responsive; no crash. |
| 2 | Tap the trigger. | The system permission dialog appears. | Dialog title matches the permission (e.g., “Allow access to camera?”). |
| 3 | Read the dialog with TalkBack enabled. | TalkBack reads the permission name, description, and each button label. | All elements are announced; focus order is logical. |
| 4 | Deny the permission (tap Deny). | Dialog disappears; app receives PERMISSION_DENIED in its callback. | App does not crash; feature is disabled or shows rationale. |
| 5 | Re‑tap the trigger. | Dialog appears again (unless “Don’t ask again” was checked). | If the checkbox was not checked, dialog shows; otherwise, system skips dialog and returns denied instantly. |
| 6 | Grant the permission (tap Allow). | Dialog disappears; app receives PERMISSION_GRANTED. | Protected API call succeeds (e.g., camera preview starts). |
| 7 | Verify permission status in Settings. | Settings → Apps → [App] → Permissions shows the permission as granted. | Matches the callback result. |
| 8 | Repeat steps 2‑7 for each permission your app requests (camera, microphone, location, notifications, etc.). | Each flow behaves as expected. | No inconsistencies across permission types. |
| 9 | Test background request: start a foreground service that requests location, then press Home. | System may show a heads‑up notification or delay dialog until the app returns to foreground. | App does not crash; service handles denial gracefully. |
| 10 | Test OEM‑specific behavior: repeat on a Samsung device and a Xiaomi device. | Dialog may have slightly different styling or additional explanations. | Core functionality (grant/deny) works; no OEM‑specific crashes. |
| 11 | Test accessibility: enable largest font, bold text, and Switch Control. | Dialog text scales, buttons remain reachable, switch can activate each option. | No clipped text; switch can complete the action. |
| 12 | Test security: attempt to read protected data before the callback returns (e.g., start camera preview immediately after triggering request). | App should either wait for the callback or throw a caught exception; no crash. | No uncaught SecurityException. |
| 13 | Clean up: clear app data and repeat the entire sequence to ensure state does not leak. | Each run starts from a clean slate. | Consistent results across runs. |
Documenting Results
Use a simple spreadsheet with columns for *Permission*, *Scenario* (grant, deny, don’t ask again, etc.), *Observed Behavior*, *Expected*, *Status*, and *Notes*. Attach screenshots or screen recordings for any failures. This documentation becomes a regression baseline for future releases.
Automated Testing Strategies
While manual testing catches UI nuances, automated tests provide repeatability and coverage for regression suites. Android offers several layers for permission testing, ranging from fast unit tests to full‑system UI automations.
Unit and Instrumentation Tests with Espresso
Espresso excels at validating that your app reacts correctly to permission callbacks, but it cannot interact with the system permission dialog directly (the dialog runs in a separate system process). Instead, you mock the permission result and verify UI changes.
// PermissionFragmentTest.kt
@RunWith(AndroidJUnit4::class)
class PermissionFragmentTest {
@get:Rule
val grantRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
@Test
fun cameraPermissionGranted_showsPreview() {
// Launch the fragment that requests camera
launchFragmentInContainer<CameraFragment>(Bundle.EMPTY)
// Espresso can see the UI that appears after permission is granted
onView(withId(R.id.camera_preview))
.check(matches(isDisplayed()))
}
@Test
fun cameraPermissionDenied_showsRationale() {
// Revoke the permission to simulate a deny scenario
InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.revokePermission(Manifest.permission.CAMERA, PackageManager.PERMISSION_DENIED)
launchFragmentInContainer<CameraFragment>(Bundle.EMPTY)
// Assuming the fragment shows a TextView with id R.id.permission_rationale
onView(withId(R.id.permission_rationale))
.check(matches(isDisplayed()))
}
}
The GrantPermissionRule automatically grants the listed permissions before each test method runs, letting you test the *granted* path without invoking the system dialog. To test the denied path, manually revoke the permission after the rule has run (as shown) or use a custom ActivityTestRule that calls Context#revokePermission.
UI Automator Tests for System Dialogs
UI Automator can cross‑process boundaries, making it suitable for clicking the Allow or Deny buttons on the system permission dialog. Note that UI Automator requires the test to be instrumented with android.permission.INTERACT_ACROSS_USERS_FULL (granted automatically for instrumentation tests on Android 9+).
// PermissionDialogTest.java
@RunWith(AndroidJUnit4::class)
public class PermissionDialogTest {
private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
private static final int REQUEST_CODE = 101;
@Before
public void revokeCameraPermission() {
Context ctx = InstrumentationRegistry.getInstrumentation()
.getTargetContext();
ctx.revokePermission(CAMERA_PERMISSION, PackageManager.PERMISSION_DENIED);
}
@Test
public void grantCameraViaUiAutomator() {
// Launch the activity that triggers the permission request
Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
InstrumentationRegistry.getInstrumentation()
.startActivitySync(intent);
// Wait for the system dialog to appear (max 2 seconds)
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject allowBtn = device.findObject(new UiSelector()
.textContains("Allow")
.className("android.widget.Button"));
assertTrue("Allow button not found", allowBtn.waitForExists(2000));
// Click Allow
allowBtn.clickAndWaitForNewWindow(2000);
// Verify that the permission is now granted
PackageManager pm = InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getPackageManager();
int granted = pm.checkPermission(CAMERA_PERMISSION,
InstrumentationRegistry.getTargetContext().getPackageName());
assertEquals(PackageManager.PERMISSION_GRANTED, granted);
}
}
Key points:
- Use
UiSelector().textContains("Allow")– the exact text may vary by locale; you can also use resource IDs if you know them (android:id/button1for Allow,android:id/button2for Deny on many devices). - After clicking, call
waitForNewWindowto ensure the dialog has dismissed before proceeding. - Verify the grant via
PackageManager#checkPermission.
Using adb to Grant/Revoke Permissions
For quick sanity checks or scripted test harnesses, adb provides direct control over permission states.
# Revoke a permission (simulate a deny)
adb shell pm revoke com.example.myapp android.permission.CAMERA
# Grant a permission (simulate an allow)
adb shell pm grant com.example.myapp android.permission.CAMERA
# Check current state
adb shell pm list permissions -g -d | grep CAMERA
You can embed these commands in a shell script that launches your app, performs a UI action via adb shell input tap (coordinates obtained from adb shell uiautomator dump), then checks the permission state. This approach is useful for continuous‑integration pipelines that run on device farms lacking UI Automator support.
Leveraging AndroidX Test Rules (GrantPermissionRule)
The GrantPermissionRule from androidx.test:core simplifies permission handling in instrumentation tests. It automatically grants the requested permissions before each test and revokes them after, ensuring isolation.
@get:Rule
val permissionRule = GrantPermissionRule.grant(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
@Test
fun locationRequest_whenGranted_showsMap() {
launchActivity<MapsActivity>()
onView(withId(R.id.map_view)).check(matches(isDisplayed()))
}
If you need to test the *denied* path, combine the rule with a manual revoke after the rule’s initialization:
@Before
fun revokeLocation() {
val ctx = ApplicationProvider.getApplicationContext()
ctx.revokePermission(Manifest.permission.ACCESS_FINE_LOCATION,
PackageManager.PERMISSION_DENIED)
}
Mocking Permission Results with Robolectric
Robolectric runs tests on the JVM, making it fast for unit‑level logic. It allows you to stub the Context#checkPermission and Context#requestPermissions calls.
@RunWith(RobolectricTestRunner.class)
@Config(sdk = {Build.VERSION_CODES.Q})
public class PermissionViewModelTest {
private PermissionViewModel viewModel;
@Before
public void setUp() {
Context ctx = RuntimeEnvironment.application;
// Pretend the permission is denied
ShadowContext shadowCtx = shadowOf(ctx);
shadowCtx.grantPermission(Manifest.permission.CAMERA,
PackageManager.PERMISSION_DENIED);
viewModel = new PermissionViewModel(ctx);
}
@Test
public void requestCameraPermission_whenDenied_returnsFalse() {
boolean granted = viewModel.requestCameraPermission();
assertFalse(granted);
}
}
Robolectric is ideal for testing ViewModels, UseCases, or any logic that decides whether to show a rationale or disable a UI component based on permission state.
CI Integration Tips
- Device farm selection – Choose a fleet that offers a range of API levels (21‑34) and at least two OEM skins (e.g., Google Pixel and Samsung Galaxy).
- Parallelization – Split the permission matrix across shards; each shard runs a subset of scenarios (grant, deny, don’t ask again) on a different device.
- Artifact collection – After each test, pull
adb logcatandadb shell dumpsys activityto capture any ANRs or crashes. Store them as build artifacts for triage. - Flaky test mitigation – UI Automator tests that interact with system dialogs can be flaky due to animation timing. Add explicit waits (
UiObject.waitForExists) and disable window animations during tests viaadb shell settings put global window_animation_scale 0.0.
Tooling Comparison
| Tool | Scope | Strengths | Weaknesses | Typical Use |
|---|---|---|---|---|
| Espresso + GrantPermissionRule | In‑app logic, UI after permission result | Fast, reliable, integrates with AndroidJUnitRunner, no cross‑process needed | Cannot interact with system dialog; limited to granted/denied mocking | Unit/UI tests that verify internal behavior given a known permission state |
| UI Automator | Cross‑process system dialogs, Settings app | Real interaction with Allow/Deny buttons, works across API levels, can launch Settings | Slower, requires INTERACT_ACROSS_USERS_FULL, can be brittle with locale/animation changes | End‑to‑end tests that validate the full permission flow including system UI |
| adb shell pm grant/revoke | Device‑level permission manipulation | Instant, scriptable, works on non‑debuggable builds (grant only if user‑initiated via Settings) | Cannot simulate user interaction with dialog; grant only works if app is debuggable or device is rooted (unless using Settings UI) | Pre‑test setup, post‑test verification, CI sanity checks |
| Robolectric | Pure Java/JVM logic, ViewModel, UseCase | Extremely fast, no emulator/device needed, easy to stub permission checks | Does not test Android framework UI or system dialogs | Unit tests of business logic that depends on permission state |
| Espresso Idling Resources + Camera2 API | Async permission‑dependent operations (e.g., camera preview) | Can wait for asynchronous results after permission granted | Requires custom idling resource implementation | Tests that need to confirm that a preview session starts only after permission is granted |
Choose the combination that matches your test pyramid: unit/logic tests with Robolectric, instrumentation tests with Espresso + GrantPermissionRule for fast feedback, and a smaller set of UI Automator tests to guard against system‑level regressions.
Autonomous, Persona‑Driven Exploration with SUSA
Autonomous testing tools that model real‑world user behaviors can uncover permission‑related defects that scripted tests never consider. SUSA (SUSATest) is an autonomous QA platform that explores an app without predefined scripts, using a set of persona profiles that mimic how different people interact with software.
How SUSA Models Different Personas
SUSA defines behavior patterns for personas such as:
- Curious – taps every visible element, explores deep hierarchies, often triggers permission requests by accident.
- Impatient – quickly dismisses dialogs, may tap outside the dialog or use the back button to escape.
- Novice – reads dialog text carefully, may need multiple attempts to understand what a permission entails.
- Adversarial – tries to bypass security controls, repeatedly denies permissions, or uses accessibility services to click hidden buttons.
- Elderly – prefers larger touch targets, may struggle with small text, often relies on TalkBack.
- Accessibility – enables TalkBack, Switch Control, or high‑contrast mode before launching the app.
- Power user – uses shortcuts, long‑presses, and expects immediate feedback; may force‑stop the app to reset permissions.
Each persona carries a probability distribution for actions like “tap Allow”, “tap Deny”, “check Don’t ask again”, “open Settings from the dialog”, or “ignore the dialog entirely”.
What It Looks For in Permission Flows
During an exploration run, SUSA monitors:
- Whether a permission dialog appears at the expected moment (e.g., after tapping a “Scan QR code” button).
- How each persona reacts to the dialog (grant, deny, dismiss, or navigate to Settings).
- Post‑dialog state: does the app crash, show an error toast, or silently continue?
- Accessibility compliance: does TalkBack read the dialog correctly under the “Accessibility” persona?
- Security signals: does any persona manage to trigger a protected API before the permission result is returned?
Because SUSA does not rely on hard‑coded test cases, it can discover edge cases such as a dialog that appears *after* the user has already navigated away from the triggering screen (a race condition caused by delayed permission requests).
Real‑World Bugs Found Only by Persona‑Driven Runs
In a recent analysis of a popular e‑commerce app, SUSA’s Impatient persona repeatedly tapped the back button while the permission dialog was still visible. This caused the activity to finish before the permission callback was delivered, leaving a fragment in a detached state and resulting in a NullPointerException when the app later tried to use the camera. The bug never appeared in scripted tests because those tests always waited for the dialog to resolve before proceeding.
Another finding came from the Elderly persona, which used the system’s magnification gesture to enlarge the dialog. On a device with a non‑standard font scaling implementation, the “Allow” button’s hit‑target shifted, making it impossible to tap without first panning the magnified view. The app logged a permission denial and showed a generic error, leading to poor conversion rates.
These examples illustrate how persona‑driven exploration surfaces issues rooted in timing, UI layout, and accessibility that deterministic scripts overlook. Integrating SUSA into your nightly regression cycle adds a layer of confidence that permission dialogs behave correctly for the full spectrum of real users.
Checklist for Permission Dialog Testing
Use this concise list before signing off a release. Tick each item only after you have verified it on at least one device representing your minimum supported API level and one OEM skin.
- [ ] Manifest audit – Every requested
is justified in the privacy policy; no excess permissions. - [ ] Dialog appearance – Permission dialog shows correct title, description, and two action buttons for each runtime request.
- [ ] Grant path – After tapping Allow, the protected API returns successfully; no
SecurityException. - [ ] Deny path – After tapping Deny, the app receives
PERMISSION_DENIEDand does not crash; UI reflects lack of access (e.g., button disabled, toast shown). - [ ] Don’t ask again – When the checkbox is checked and Deny is selected, subsequent requests skip the dialog and return denied instantly.
- [ ] Settings revisit – Tapping a “Go to Settings” link (if provided) opens the correct App info → Permissions screen; toggling the switch there updates the app’s state without requiring a restart.
- [ ] Background requests – Permission requests originating from a foreground service or background worker do not cause ANRs; the app handles deferral or immediate denial gracefully.
- [ ] Rapid successive requests – Multiple permission dialogs triggered in quick succession are displayed sequentially; each request’s result is tracked correctly.
- [ ] Configuration change – Rotating the device or entering multi‑window mode while a dialog is visible does not dismiss it or lose the request context.
- [ ] Accessibility – With TalkBack, Switch Control, large font, and bold text enabled, the dialog is readable, navigable, and operable.
- [ ] OEM variations – Tested on at least two OEM skins (e.g., Stock Android, Samsung OneUI, Xiaomi MIUI) – dialog appearance and behavior remain consistent.
- [ ] Security audit – No protected data is accessed before the permission callback returns; logs do not contain PII from permission‑related calls.
- [ ] Automated coverage – At least one unit test (Robolectric) validates logic for granted/denied states; one instrumentation test (Espresso + GrantPermissionRule) checks UI after grant; one UI Automator test verifies interaction with the system dialog.
- [ ] CI gate – Permission‑related test suite runs on every pull request; failures block merge.
Key Takeaways
Permission dialogs are a nexus of user trust, platform security, and app functionality. A disciplined testing strategy combines:
- Clear matrix‑driven test design that covers happy paths, error paths, edge cases, accessibility, and security.
2
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free