Best Practices for Managing Android App Permissions

App permissions safeguard sensitive user data and should only be requested when essential for core functionality. Often, equivalent or superior results can be achieved without accessing protected information by leveraging alternatives like system intents, scoped identifiers, or indirect APIs.

Core Principles for Permission Usage

  1. Request Only Essential Permissions Evaluate whether your use case truly requires direct access to sensitive data. Alternatives such as implicit intents, advertising IDs, or audio focus APIs may fulfill the need without requesting permissions.

  2. Audit Third-Party Libraires Libraries often declare permissions in their manifests. Review each dependency’s permission requirements and ensure they align with your app’s functionality. Prefer lightweight SDKs that avoid unnecessary permissions.

  3. Be Transparent with Users Clearly explain why a permission is needed—both before requesting it and if the user denies it. Contextual justification increases user trust and acceptance rates.

  4. Provide Ongoing Indicators for Sensitive Access When using hardware like the camera or microphone, display persistent indicators (e.g., status bar icons) so users know data collection is active, preventing perceptions of covert behavior.

Runtime Permissions in Android 6.0+

Starting with Android 6.0 (API level 23), permissions are granted at runtime rather than install time. This shift introduces key considerations:

  • Context Matters: Users expect permission requests to align with immediate app actions. If a request seems unrelated (e.g., location access in a calculator app), provide an in-app explanation.
  • Graceful Degradation: Handle cases where users deny or revoke permissions. Monitor denial rates (e.g., via analytics) to refine UX or reduce dependency on sensitive APIs.
  • Minimize Request Volume: Each permission increases user friction. Request only what’s necessary to reduce abandonment risk.

Special Handling for Call Log and SMS Permissions

Google Play restricts apps from requesting READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, or WRITE_SMS unless the app is set as the default handler for calls or messaging. Always prompt users to configure default handlers before requesting these permissions.

Alternatives to Common Permission Requests

Use Intents Instead of Direct Access

Instead of requesting CAMERA, launch the system camera via MediaStore.ACTION_IMAGE_CAPTURE. The user controls what to share, and no permission is needed:

val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (captureIntent.resolveActivity(packageManager) != null) {
    startActivityForResult(captureIntent, REQUEST_IMAGE_CAPTURE)
}

Similarly, use Intent.ACTION_DIAL for phone calls or Intent.ACTION_PICK for contacts—avoiding CALL_PHONE or READ_CONTACTS.

Manage Audio Focus Without Phone State Permissions

To pause media during calls, request audio focus instead of monitoring phone state:

val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
    .setOnAudioFocusChangeListener { focus ->
        if (focus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            mediaPlayer.pause()
        }
    }
    .build()

audioManager.requestAudioFocus(focusRequest)

This avoids requiring READ_PHONE_STATE and works across all Android versions.

Generate App-Scoped Device Identifiers

Avoid using IMEI or other hardware IDs (which require PHONE permissions). Instead:

  • Use UUID.randomUUID().toString() stored in app preferences.
  • For cloud-synced device profiles, generate a random ID on first launch and persist it securely.

Use Advertising ID for Analytics

For ad personalization or anonymous analytics, use the Google Play Services Advertising ID:

CoroutineScope(Dispatchers.IO).launch {
    val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context)
    val adId = adInfo.id
    // Use adId for tracking
}

Never access this from the main thread, and respect the user’s "opt-out of ads personalization" setting.

Justify Permission Requests

The system dialog doesn’t explain why a permission is needed. Preempt confusion by showing a rationale UI before calling requestPermissions():

if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
    showExplanationDialog { requestLocationPermission() }
} else {
    requestLocationPermission()
}

For SMS-based verification on Android 8.0+, avoid READ_SMS. Instead, use:

val token = createAppSpecificSmsToken(context, "com.example.myapp")
// Send token to your backend; it will be included in the SMS

Testing Permission Scenarios

Test your app under various permission states:

  • Grant/deny permissions via Settings.
  • Use ADB to simulate configurations:
    adb shell pm grant com.example.app android.permission.CAMERA
    adb shell pm revoke com.example.app android.permission.ACCESS_FINE_LOCATION
    
  • Verify all user flows function correctly when permissions are missing.

Ensure fallback behaviors are intuitive—e.g., disable features gracefully or guide users to enable required permissions.

Tags: Android permissions mobile security Best Practices Privacy

Posted on Fri, 18 Sep 2026 16:17:45 +0000 by Gmans