Generated banner for Android Permissions Security
Generated logo for Android Permissions Security

Android Permissions Security

Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)

  • Skill
  • Guidelines
  • Security
  • Apache-2.0
  • No issues found

From the author

Audits, detects gaps, and remediates Android permissions and IPC component security vulnerabilities. Use when reviewing AndroidManifest.xml, defining custom permissions, securing Services, Receivers, and Providers, verifying caller signatures and UID using Binder, or implementing runtime permission flows.
— the description by Google LLC

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a systematic approach to identifying and fixing security gaps in Android applications. It covers the secure configuration of AndroidManifest.xml, the implementation of robust IPC (Inter-Process Communication) boundaries, and the correct handling of runtime permissions. It emphasizes moving away from insecure practices like spoofable intent-based identity checks toward cryptographically secure UID verification using Binder. The guide also details how to properly define custom permissions, manage granular URI grants, and implement safe, non-blocking runtime permission request flows.

What it does

  • Audits AndroidManifest.xml for weak custom permission protection levels.
  • Provides patterns for cryptographically verifying caller identity via Binder.
  • Guides the implementation of granular URI permission management for ContentProviders.
  • Defines secure practices for protecting exported Services, Receivers, and Providers.
  • Outlines a 3-state runtime permission request flow to prevent infinite loops.
  • Recommends zero-permission alternatives like the Photo Picker for media access.

When to use it

  • During security audits of Android application codebases.
  • When configuring custom permissions for inter-app communication.
  • When securing AIDL-based bound services against Confused Deputy attacks.
  • When refactoring legacy code that relies on spoofable intent extras for identity.
  • When implementing or updating runtime permission request logic.

Advantages

  • Prevents common IPC vulnerabilities like package name spoofing.
  • Enforces the principle of least privilege through granular URI grants.
  • Reduces user friction by implementing standard, non-looping permission flows.
  • Eliminates Confused Deputy risks by using proper Binder permission enforcement.
  • Provides clear, actionable code snippets for modern Android security APIs.

Usage examples

“How do I securely verify that a caller is an authorized partner app?”
The agent provides a CallerVerifier implementation that uses Binder.getCallingUid() and PackageManager.hasSigningCertificate() to cryptographically verify the caller's signature.
“My app is vulnerable to intent spoofing. How do I fix it?”
The agent instructs you to remove all string-based package checks and replace them with Binder-based UID authentication to ensure the caller's identity is genuine.
“How should I handle runtime permissions without creating an infinite loop?”
The agent provides a 3-state flow pattern: checking grant status, showing rationale if needed, and launching the request, with a fallback to Settings for permanent denials.
“How do I restrict access to my ContentProvider to only specific partner apps?”
The agent guides you to define a custom permission with signature|knownSigner protection level and apply it to the provider, ensuring only apps with matching certificate digests can access it.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/android/android-permissions-security/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip android-permissions-security.zip -d .agents/skills

Documentation by Google LLC

Guidelines for auditing permission vulnerabilities, enforcing least privilege, securing IPC boundaries, and handling runtime permissions across the Android ecosystem.

Glossary

  • Runtime Permission: Dangerous permission (e.g., Camera, Location) that must be granted by the user at runtime on Android 6.0+ (API 23+).

  • Custom Permission: A permission defined by an app (<permission>) to restrict access to its exported components.

  • Protection Level: Attribute defining how the OS grants the permission (normal, dangerous, signature, knownSigner).

  • knownSigner: Protection flag (API 31+) granting signature-level access to partner apps matching trusted certificate digests.

  • SigningInfo: Object (API 28+) providing signing history and multiple signer certificates for an application.

  • Binder Calling Identity: Reliable UID representing the calling process obtained using Binder.getCallingUid().

  • URI Permission: Temporary, granular access granted to a content URI using grantUriPermissions and intent flags.

Prerequisites

  • Add androidx.core:core-ktx and androidx.activity:activity-ktx (or androidx.fragment:fragment-ktx) dependencies to the app-level build.gradle file for ContextCompat and ActivityResultContracts.

  • For knownSigner partner permissions (android:knownCerts), ensure the project compileSdk and targetSdk are at least 31 (Android 12).

  • For broadcast identity sharing (BroadcastOptions.setShareIdentityEnabled), ensure compileSdk and targetSdk are at least 34 (Android 14).


1. Security audit and permission gap detection playbook

When auditing an Android project for permission and IPC security vulnerabilities, systematically inspect each area:

Vulnerability AreaSearch In / PatternRiskRemediation Action
Weak Custom Permission ProtectionAndroidManifest.xml with android:protectionLevel="normal", "dangerous", or "signatureOrSystem"Unauthorized apps request or claim permission without restrictionUpgrade to signature or signature with knownSigner and android:knownCerts.
Overly Broad URI Permission Grants<grant-uri-permission android:pathPrefix="/" /> or pathPrefix="" in <provider>Leaks all provider data to third partiesReplace with explicit scoped subpath (e.g. android:pathPrefix="/shared/"). Retain android:grantUriPermissions="true". Remove any wildcard pathPrefix="/".
Missing ContentProvider Split Permissions<provider> with only generic android:permission or missing android:readPermission and android:writePermissionRead-only callers execute write operations or write-only callers access read operationsConfigure granular android:readPermission="...READ_DATA" and android:writePermission="...WRITE_DATA", AND declare the custom <permission> tags with android:protectionLevel="signature" (or signature with knownSigner) at the <manifest> root.
Spoofable Caller Identity Checksintent.getStringExtra("calling_package"), intent.getStringExtra("sender"), callingPackage == "..." in ServicesMalicious callers forge intent extras or stringsRemove string checks; authenticate caller UID cryptographically using Binder.getCallingUid() and pm.hasSigningCertificate().
Bound Service Permission BypasscheckCallingOrSelfPermission() or enforceCallingOrSelfPermission() in Binder AIDL stubsFalls back to host app UID outside IPC or when identity is cleared, causing Confused Deputy bypassReplace with enforceCallingPermission("...WRITE_DATA", ...) or checkCallingPermission("...WRITE_DATA").
Missing Method-Level Checks in Bound ServicesBound Service AIDL methods mutating state without enforcing write permissionsCallers with read-only binding access invoke mutating write operationsAdd enforceCallingPermission("...WRITE_DATA", "Caller lacks WRITE_DATA permission") inside mutating AIDL methods.
Flawed Runtime Permission FlowMissing shouldShowRequestPermissionRationale(), missing permanent denial handling, or re-requesting in loopsPoor user experience, infinite loops, or inability to recover permissionsImplement 3-state flow: check grant -> check rationale dialog -> launch contract. On permanent denial, redirect to Settings.ACTION_APPLICATION_DETAILS_SETTINGS.
Simultaneous Location RequestsarrayOf(ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION) in one requestRejected by Android 11+ (API 30+)Request foreground location (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) first. Request ACCESS_BACKGROUND_LOCATION only after foreground is granted in a separate user step.
Broad Storage Permissions for MediaRequesting READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES for user image selectionOver-privilege, user privacy violationMigrate to zero-permission Photo Picker (ActivityResultContracts.PickVisualMedia).
Cached Permission StateStoring permission status in var cachedLocationPermissionGranted = ...Fails when permission is revoked or expiresQuery ContextCompat.checkSelfPermission() dynamically at invocation time and wrap protected calls in try-catch blocks catching SecurityException.
Insecure BroadcastssendBroadcast(intent) without package target or receiver permission; receiver without sender authenticationData interception or command spoofingSet explicit package (intent.setPackage(...)), pass receiver permission, and enable BroadcastOptions.setShareIdentityEnabled(true) (API 34+) to verify sentFromPackage.

2. Custom permissions and protection levels

When declaring custom permissions or partner certificates in the manifest, you must follow the instructions in the <permission> element guide.

  • signature: Restricts access to apps signed with the same developer key.

  • signature|knownSigner (API 31+): Restricts access to partner apps whose signing certificate digests match declared hashes in android:knownCerts.

  • normal or dangerous : Do not use for private inter-app IPC.

  • signatureOrSystem : Deprecated (API 23). Upgrade deprecated signatureOrSystem permissions to signature. Avoid in non-system apps.

Manifest snippets
Signature-level custom permission

Use signature permissions when restricting component access exclusively to apps signed with the same developer certificate by declaring the <permission> in the manifest and applying android:permission to the target component.

<permission
    android:name="com.example.snippets.permission.ACCESS_SECURE_API"
    android:protectionLevel="signature" />

<br />

KnownSigner partner permission (API 31+)

Use signature|knownSigner permissions when granting access to specific external partner apps by declaring the <permission> in the manifest with android:knownCerts referencing a resource array of trusted SHA-256 certificate digests.

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- SHA-256 fingerprints of trusted partner certificates (lowercase, no colons) -->
    <string-array name="trusted_partner_certs" tools:ignore="Typos">
        <item>103938ee4537e59e8ee792f654504fb8346fc6b346d0bbc4415fc339fcfc8ec1</item>
    </string-array>
</resources>

<br />

<permission
    android:name="com.example.snippets.permission.PARTNER_API"
    android:protectionLevel="signature|knownSigner"
    android:knownCerts="@array/trusted_partner_certs" />

<br />


3. Exported component protection and URI permissions

  • Private components : Always set android:exported="false".

  • Exported components : Require permissions (android:permission, android:readPermission, android:writePermission).

  • Granular URI Grants : Retain android:grantUriPermissions="true" for secure sharing, but scope grants to specific sub-paths. Never use wildcard pathPrefix="/".

  • Custom Permission Declarations : When splitting read and write permissions on a provider, you MUST declare the <permission> tags with android:protectionLevel="signature" (or signature|knownSigner) at the <manifest> root level.

  • Remediating Existing Wildcard Grants : You MUST find and remove any existing <grant-uri-permission android:pathPrefix="/" /> or pathPrefix="" and replace it with an explicit scoped subpath (e.g. android:pathPrefix="/shared/").

  • Granular UriPermission Management : Instead of broad manifest grants, use runtime android.content.UriPermission tokens for least-privilege per-URI sharing:

    • Grant temporary access : context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) or attach Intent.FLAG_GRANT_READ_URI_PERMISSION to outgoing intents.

    • Persist and release access : The receiving app calls contentResolver.takePersistableUriPermission(uri, modeFlags) for durable access across reboots, and contentResolver.releasePersistableUriPermission(uri, modeFlags) when durable access is no longer needed.

    • Audit active grants : Inspect contentResolver.persistedUriPermissions (List<UriPermission>) to verify granted URIs (uriPermission.uri, uriPermission.isReadPermission(), uriPermission.isWritePermission()).

    • Revoke temporary access : Call context.revokeUriPermission(targetPackage, uri, modeFlags) (API 26+), falling back to context.revokeUriPermission(uri, modeFlags) on API 25 and lower so access fails closed as soon as operations conclude.

Manifest snippet: Complete secure ContentProvider declaration

Declare the split read and write permissions at the <manifest> root:

<permission
    android:name="com.example.snippets.permission.READ_DATA"
    android:protectionLevel="signature" />
<permission
    android:name="com.example.snippets.permission.WRITE_DATA"
    android:protectionLevel="signature" />

<br />

Then configure the provider with those permissions and a scoped URI grant:

<provider
    android:name=".permissions.SecureDataProvider"
    android:authorities="com.example.snippets.partnerprovider"
    android:exported="true"
    android:readPermission="com.example.snippets.permission.READ_DATA"
    android:writePermission="com.example.snippets.permission.WRITE_DATA"
    android:grantUriPermissions="true">
    <grant-uri-permission android:pathPrefix="/shared/" />
</provider>

<br />

Runtime granular UriPermission management snippet
object UriPermissionManager {

    /** Grants temporary read access to a specific URI for a partner package. */
    fun grantScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
        context.grantUriPermission(
            targetPackage,
            uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION
        )
    }

    /** Audits active persisted URI permissions held by the application. */
    fun getActivePersistedPermissions(context: Context): List<UriPermission> {
        return context.contentResolver.persistedUriPermissions
    }

    /** Revokes temporary URI access for a specific partner package when data transfer completes. */
    fun revokeScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.revokeUriPermission(
                targetPackage,
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
        } else {
            context.revokeUriPermission(
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
        }
    }
}

<br />


4. Cryptographic caller identity verification

Never trust caller package names provided in Intents or string arguments (getCallingPackage(), intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), intent.getStringExtra("package")). Intent extras and string package names are forged by malicious applications.

Critical vulnerability: Package name spoofing using intent extras
// VULNERABLE PATTERN: DO NOT DO THIS
val callingPackage = intent?.getStringExtra("calling_package")
if (callingPackage != null && SignatureUtils.verifyPartnerPackage(this, callingPackage)) {
    // A malicious app passes "com.example.partner" in the extra.
    // The signature check verifies the installed partner on disk, but the CALLER was malicious!
}

<br />

Secure pattern: Authenticate calling UID cryptographically
  1. Completely remove all intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), and callingPackage == "..." checks.

  2. Obtain caller identity using Binder.getCallingUid() inside an active Binder IPC transaction (such as an AIDL Stub method or ContentProvider method).

  3. Verify caller certificate using PackageManager.hasSigningCertificate() (API 28+) or PackageManager.getPackagesForUid().

Complete CallerVerifier implementation

Apply this to the service that currently performs the spoofable check.

  • Placement : declare CallerVerifier in the same file as the service you are fixing (for example inside SecureDataService.kt). Do not move the verification into a new file and leave the original service untouched.

  • Active Binder IPC context : Binder.getCallingUid() only returns the remote caller's UID during an active Binder transaction (never inside Service.onStartCommand() or Service.onBind(), where it returns Process.myUid()). Reject callingUid == Process.myUid() so calls outside an active IPC transaction or after Binder.clearCallingIdentity() fail closed instead of bypassing verification.

  • Nullability : packageInfo.signatures is Array<Signature>?. Copy the ?: return false guard exactly as written below. Iterating packageInfo.signatures directly fails to compile with "Non-nullable value required to call an 'iterator()' method in a for-loop."

object CallerVerifier {
    private const val TRUSTED_PARTNER_SHA256 =
        "A1B2C3D4E5F60708090A0B0C0D0E0F1011121314151617181920212223242526"

    fun isCallerAuthorized(context: Context): Boolean {
        val callingUid = Binder.getCallingUid()
        if (callingUid == android.os.Process.myUid()) return false

        val pm = context.packageManager
        // Modern API 28+ check by UID:
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val certBytes = hexStringToByteArray(TRUSTED_PARTNER_SHA256)
            return pm.hasSigningCertificate(callingUid, certBytes, PackageManager.CERT_INPUT_SHA256)
        }

        // Fallback for legacy APIs:
        val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
        for (pkg in callingPackages) {
            if (verifyPackageSignature(pm, pkg)) {
                return true
            }
        }
        return false
    }

    @Suppress("DEPRECATION")
    private fun verifyPackageSignature(pm: PackageManager, packageName: String): Boolean {
        return try {
            val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
            val signatures = packageInfo.signatures ?: return false
            for (sig in signatures) {
                val digest = java.security.MessageDigest.getInstance("SHA-256").digest(sig.toByteArray())
                val hex = digest.joinToString("") { "%02X".format(it) }
                if (hex.equals(TRUSTED_PARTNER_SHA256, ignoreCase = true)) return true
            }
            false
        } catch (e: PackageManager.NameNotFoundException) {
            false
        }
    }

    private fun hexStringToByteArray(s: String): ByteArray {
        val len = s.length
        val data = ByteArray(len / 2)
        for (i in 0 until len step 2) {
            data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
        }
        return data
    }
}

<br />


5. Method-level Binder enforcement in bound services (SecureBoundService.kt)

In bound services exposing AIDL Binder stubs, protect state-modifying methods using enforceCallingPermission().

Critical security rule: Never use CallingOrSelfPermission in services
  • NEVER use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission().

  • Why: During an active Binder transaction, this method checks the caller's permission. However, if the method is invoked locally (outside of an active IPC) or if the IPC calling identity is dropped, it falls back to checking the host app's permissions. Because the host app holds the permission, this fallback grants access and bypasses the security boundary, causing a Confused Deputy vulnerability.

  • MUST USE: enforceCallingPermission("com.example.snippets.permission.WRITE_DATA", "Caller lacks WRITE_DATA permission") or checkCallingPermission("com.example.snippets.permission.WRITE_DATA").

Complete SecureBoundService implementation
class SecureBoundService : Service() {

    private val binder = object : IMyService.Stub() {
        override fun getData(): String {
            // Read-only operation guarded by manifest-level permission
            return "Confidential Data"
        }

        override fun modifyData(newData: String) {
            // MUST use enforceCallingPermission or checkCallingPermission.
            // NEVER use checkCallingOrSelfPermission or enforceCallingOrSelfPermission.
            [email protected](
                "com.example.snippets.permission.WRITE_DATA",
                "Caller lacks WRITE_DATA permission"
            )
            updateInternalState(newData)
            Log.d("SecureBoundService", "Data modified to: $newData with proper WRITE_DATA permission check")
        }
    }

    override fun onBind(intent: Intent?): IBinder = binder

    private fun updateInternalState(data: String) {
        // Internal state update logic
    }
}

<br />


6. Safe runtime permission workflow (RuntimePermissionsActivity.kt)

When implementing runtime permission requests or handling permission denial states, you must follow the instructions in the Request runtime permissions guide and Permissions overview. Implement a 3-state flow without caching states or creating infinite retry loops:

  1. Check Granted State : ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED.

  2. Check Rationale : When shouldShowRequestPermissionRationale(permission) returns true, show a rationale dialog and launch the permission request only from its Continue button.

  3. Launch Contract : ActivityResultContracts.RequestPermission().

  4. Handle Permanent Denial : In the callback, if permission was denied and !shouldShowRequestPermissionRationale(permission), guide the user to Settings.ACTION_APPLICATION_DETAILS_SETTINGS.

Complete RuntimePermissionsActivity.kt implementation
class RuntimePermissionsActivity : ComponentActivity() {

    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                startCameraPreview()
            } else {
                if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                    // User selected 'Don't ask again' or permanently denied.
                    // Direct user to Application Details Settings.
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                        data = Uri.fromParts("package", packageName, null)
                    }
                    startActivity(intent)
                } else {
                    showSnackbar("Camera permission is required to preview camera feed.")
                }
            }
        }

    fun requestCameraPermissionSafely() {
        val permission = Manifest.permission.CAMERA
        when {
            ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
                startCameraPreview()
            }
            shouldShowRequestPermissionRationale(permission) -> {
                showRationaleDialog(permission)
            }
            else -> {
                cameraLauncher.launch(permission)
            }
        }
    }

    private fun showRationaleDialog(permission: String) {
        AlertDialog.Builder(this)
            .setTitle("Camera permission required")
            .setMessage("Camera permission is needed to preview the camera feed.")
            .setPositiveButton("Continue") { _, _ ->
                cameraLauncher.launch(permission)
            }
            .setNegativeButton("Cancel", null)
            .show()
    }

    private fun startCameraPreview() {
        Log.d("RuntimePermissionsActivity", "Camera preview started")
    }

    private fun showSnackbar(msg: String) {
        Log.i("RuntimePermissionsActivity", msg)
    }
}

<br />


7. Zero-permission alternatives and sequential flows

When implementing photo or media selection, you must follow the instructions in the Photo Picker API guide to avoid requesting broad storage permissions.

  • Photo Picker : Prefer ActivityResultContracts.PickVisualMedia() over requesting wide storage permissions (READ_MEDIA_IMAGES, READ_EXTERNAL_STORAGE).

  • Sequential Location Flow : Request ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION first. Never request ACCESS_BACKGROUND_LOCATION simultaneously. Only request background location in a separate step after foreground is granted.

Photo picker implementation
private val photoPickerLauncher =
    registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
        if (uri != null) {
            handleImageUri(uri)
        }
    }

fun selectPhoto() {
    photoPickerLauncher.launch(
        PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
    )
}

<br />

Sequential location request implementation
private val foregroundLocationLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
        val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
        if (fineGranted || coarseGranted) {
            startForegroundLocationUpdates()
        }
    }

fun requestForegroundLocation() {
    foregroundLocationLauncher.launch(
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    )
}

// Background location requested only AFTER foreground is granted and user explicitly opts in:
private val backgroundLocationLauncher =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            startBackgroundTracking()
        }
    }

<br />


8. Stateless checks and error boundaries

  • Never cache permission states in in-memory variables (e.g. cachedLocationPermissionGranted). Check ContextCompat.checkSelfPermission() dynamically before each operation.

  • Wrap API calls in try-catch : Catch SecurityException around permission-protected framework calls (e.g., location queries) to handle one-time permission revocation gracefully.

fun performLocationAccess() {
    val fine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    val coarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
    if (fine != PackageManager.PERMISSION_GRANTED && coarse != PackageManager.PERMISSION_GRANTED) {
        requestForegroundLocation()
        return
    }

    val provider = if (fine == PackageManager.PERMISSION_GRANTED) {
        LocationManager.GPS_PROVIDER
    } else {
        LocationManager.NETWORK_PROVIDER
    }

    try {
        val location = locationManager.getLastKnownLocation(provider)
        processLocation(location)
    } catch (e: SecurityException) {
        Log.e("LocationAccess", "Permission revoked at runtime", e)
    }
}

<br />


9. Secure broadcasts (API 34+ share identity)

Use explicit intents, required receiver permissions, or Android 14+ broadcast options:

// Sender: Enforce permission and share identity
val intent = Intent("com.example.snippets.permission.ACTION_SECRET_UPDATE").apply {
    setPackage("com.example.partner") // Explicit target
}
val isUdc = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
val options = if (isUdc) {
    BroadcastOptions.makeBasic().apply {
        setShareIdentityEnabled(true)
    }.toBundle()
} else null

sendBroadcast(intent, "com.example.snippets.permission.RECEIVE_SECRET_UPDATE", options)

<br />

// Receiver: Validate sender on Android 14+
class ProtectedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == "com.example.snippets.permission.ACTION_SECRET_UPDATE") {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                val sender = sentFromPackage
                if (sender != "com.example.trusted_sender") {
                    return // Reject unauthorized sender
                }
            }
            processUpdate(intent)
        }
    }

    private fun processUpdate(intent: Intent) {}
}

<br />

Legacy compatibility (Pre-API 34) : On API 33 and lower, never authenticate a broadcast sender using PendingIntent.getCreatorPackage() or PendingIntent.getCreatorUid(), because any app that acquires a trusted app's PendingIntent can attach and send it (see Sender of Pending Intents). Instead, restrict the <receiver> with a signature or signature|knownSigner (API 31+) permission in the manifest; if minSdkVersion is lower than 31, use a bound Service or ContentProvider and verify Binder.getCallingUid().


Quality checklist

DO
  • MUST declare <permission android:name="..." android:protectionLevel="signature" /> (or signature|knownSigner) at the <manifest> root for custom permissions and ContentProvider split permissions.

  • MUST explicitly set android:exported="false" for all private components to remove the need for custom permission management.

  • MUST authenticate callers cryptographically using Binder.getCallingUid() and certificate hashes (PackageManager.hasSigningCertificate or getPackagesForUid) rather than checking spoofable intent extras.

  • MUST check shouldShowRequestPermissionRationale() before runtime prompts and during denial handling.

  • MUST direct user to Settings.ACTION_APPLICATION_DETAILS_SETTINGS when permission is permanently denied.

  • MUST enforce method-level checks with enforceCallingPermission() or checkCallingPermission() on modifying AIDL methods.

  • MUST scope ContentProvider URI permissions to explicit sub-paths (e.g. android:pathPrefix="/shared/") while retaining android:grantUriPermissions="true".

  • MUST catch SecurityException around protected API calls.

  • MUST request foreground and background location permissions sequentially, never simultaneously.

NEVER
  • NEVER extract or verify package names from Intent extras (intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), intent.getStringExtra("package")). Always authenticate using Binder.getCallingUid() and certificates.

  • NEVER authenticate a broadcast or IPC sender using PendingIntent.getCreatorPackage() or PendingIntent.getCreatorUid().

  • NEVER remediate caller spoofing by merely adding manifest permissions while keeping spoofable intent extra checks.

  • NEVER use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission() to guard external caller methods. Always use enforceCallingPermission() or checkCallingPermission().

  • NEVER use deprecated signatureOrSystem.

  • NEVER declare or retain <grant-uri-permission android:pathPrefix="/" /> or pathPrefix="". Always replace with a scoped sub-path.

  • NEVER create infinite permission request loops on denial.

  • NEVER request foreground and background location permissions in the same request.

  • NEVER cache permission grant states in in-memory fields.

Files

references/android/training/permissions/requesting.md

Video

Every Android app runs in a limited-access sandbox. If your app needs to use resources or information outside of its own sandbox, you can declare a runtime permission and set up a permission request that provides this access. These steps are part of the workflow for using permissions.

If you declare any dangerous permissions, and if your app is installed on a device that runs Android 6.0 (API level 23) or higher, you must request the dangerous permissions at runtime by following the steps in this guide.

If you don't declare any dangerous permissions, or if your app is installed on a device that runs Android 5.1 (API level 22) or lower, the permissions are automatically granted, and you don't need to complete any of the remaining steps on this page.

[!NOTE] Note: Some permissions guard access to system resources that are particularly sensitive or are not directly related to user privacy. There's a different process for requesting these special permissions.

Basic principles

The basic principles for requesting permissions at runtime are as follows:

  • Ask for a permission in context, when the user starts to interact with the feature that requires it.

  • Don't block the user. Always provide the option to cancel an educational UI flow, such as a flow that explains the rationale for requesting permissions.

  • If the user denies or revokes a permission that a feature needs, gracefully degrade your app so that the user can continue using your app, possibly by disabling the feature that requires the permission.

  • Don't assume any system behavior. For example, don't assume that permissions appear in the same permission group. A permission group merely helps the system minimize the number of system dialogs that are presented to the user when an app requests closely related permissions.

Workflow for requesting permissions

Before you declare and request runtime permissions in your app, evaluate whether your app needs to do so. You can fulfill many use cases in your app, such as taking photos, pausing media playback, and displaying relevant ads, without needing to declare any permissions.

If you conclude that your app needs to declare and request runtime permissions, complete these steps:

  1. Declare the permissions your app might need to request in your app's manifest file.

  2. Design your app's user experience (UX) so that specific actions in your app are associated with specific runtime permissions. Let users know which actions might require them to grant permission for your app to access private user data.

  3. Wait for the user to invoke the task or action in your app that requires access to specific private user data. At that time, your app can request the runtime permission that is required for accessing that data.

  4. Check whether the user has already granted

    runtime permission your app requires. If so, your app can access the private user data. If not, continue to the next step.

    You must check whether you have a permission every time you perform an operation that requires that permission.

  5. Check whether your app should show a rationale to the user, explaining why your app needs the user to grant a particular runtime permission. If the system determines that your app shouldn't show a rationale, continue to the next step directly, without showing a UI element.

    If the system determines that your app should show a rationale, however, present the rationale to the user in a UI element. In this rationale, clearly explain what data your app is trying to access and what benefits the app can provide to the user if they grant the runtime permission. After the user acknowledges the rationale, continue to the next step.

  6. Request the runtime permission your app requires to access the private user data. The system displays a runtime permission prompt, such as the one shown on the permissions overview page.

  7. Check the user's response---whether they chose to grant or deny the runtime permission.

  8. If the user granted the permission to your app, you can access the private user data. If the user denied the permission instead, gracefully degrade your app experience so that it provides functionality to the user without the information that's protected by that permission.

Figure 1 illustrates the workflow and set of decisions associated with this process: Image: A flowchart illustrating the decision process for requesting runtime permissions, starting with an app action, checking if permission is granted, checking if rationale is needed, requesting permission, and handling the user's response. Figure 1. Diagram that shows the workflow for declaring and requesting runtime permissions on Android.

Determine whether your app was already granted the permission

To check whether the user already granted your app a particular permission, pass that permission into the ContextCompat.checkSelfPermission() method. This method returns either PERMISSION_GRANTED or PERMISSION_DENIED, depending on whether your app has the permission.

Explain why your app needs the permission

The permissions dialog shown by the system when you call requestPermissions() says what permission your app wants, but doesn't say why. In some cases, the user might find that puzzling. It is a good idea to explain to the user why your app wants the permissions before you call requestPermissions().

Research shows that users are much more comfortable with permissions requests if they know why the app needs them, such as whether the permission is needed to support a core feature of the app or for advertising. As a result, if you are only using a fraction of the API calls that fall under a permission group, explicitly list which of those permissions you are using and why. For example, if you are only using coarse location, let the user know this in your app description or in help articles about your app.

Under certain conditions, it is also helpful to let users know about sensitive data access in real time. For example, if you are accessing the camera or microphone, consider letting the user know by using a notification icon somewhere in your app, or in the notification tray (if the application is running in the background), so it doesn't seem like you are collecting data surreptitiously.

[!NOTE] Note: Starting in Android 12 (API level 31), privacy indicators notify the user whenever applications access the microphone or camera.

If you need to request a permission for a feature to work, but the reason is not clear to the user, find a way to explain why you need sensitive permissions.

If the ContextCompat.checkSelfPermission() method returns PERMISSION_DENIED, call shouldShowRequestPermissionRationale(). If this method returns true, show an educational UI to the user. In this UI, describe why the feature that the user wants to enable needs a particular permission.

Additionally, if your app requests a permission related to location, microphone, or camera, consider explaining why your app needs access to this information.

Request permissions

After the user views an educational UI, or the return value of shouldShowRequestPermissionRationale() indicates that you don't need to show an educational UI, request the permission. Users see a system permission dialog, where they can choose whether to grant a particular permission to your app.

To do this, use the RequestPermission contract, included in an AndroidX library, where you allow the system to manage the permission request code for you. Because using the RequestPermission contract simplifies your logic, it is the recommended solution when possible. However, if needed you can also manage a request code yourself as part of the permission request and include this request code in your permission callback logic.

Allow the system to manage the permission request code

To allow the system to manage the request code that is associated with a permissions request, add dependencies on the following libraries in your module's build.gradle file:

You can then use one of the following classes:

The following steps show how to use the RequestPermission contract. The process is nearly the same for the RequestMultiplePermissions contract.

  1. In your activity or fragment's initialization logic, pass in an implementation of ActivityResultCallback into a call to registerForActivityResult(). The ActivityResultCallback defines how your app handles the user's response to the permission request.

    Keep a reference to the return value of registerForActivityResult(), which is of type ActivityResultLauncher.

  2. To display the system permissions dialog when necessary, call the launch() method on the instance of ActivityResultLauncher that you saved in the previous step.

    After launch() is called, the system permissions dialog appears. When the user makes a choice, the system asynchronously invokes your implementation of ActivityResultCallback, which you defined in the previous step. Note: Your app cannot customize the dialog that appears when you call launch(). To provide more information or context to the user, change your app's UI so that it's easier for users to understand why a feature in your app needs a particular permission. For example, you might change the text in the button that enables the feature.

    Also, the text in the system permission dialog references the permission group associated with the permission that you requested. This permission grouping is designed for system ease-of-use, and your app shouldn't rely on permissions being within or outside of a specific permission group.

The following code snippet shows how to handle the permissions response:

Kotlin
when {
    ContextCompat.checkSelfPermission(
            CONTEXT,
            Manifest.permission.REQUESTED_PERMISSION
            ) == PackageManager.PERMISSION_GRANTED -> {
        // You can use the API that requires the permission.
        performAction(...)
    }
    ActivityCompat.shouldShowRequestPermissionRationale(
            this, Manifest.permission.REQUESTED_PERMISSION) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected, and what
        // features are disabled if it's declined. In this UI, include a
        // "cancel" or "no thanks" button that lets the user continue
        // using your app without granting the permission.
        showInContextUI(...)
    }
    else -> {
        // You can directly ask for the permission.
        requestPermissions(CONTEXT,
                arrayOf(Manifest.permission.REQUESTED_PERMISSION),
                REQUEST_CODE)
    }
}
Java
if (ContextCompat.checkSelfPermission(
        CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
        PackageManager.PERMISSION_GRANTED) {
    // You can use the API that requires the permission.
    performAction(...);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
        this, Manifest.permission.REQUESTED_PERMISSION)) {
    // In an educational UI, explain to the user why your app requires this
    // permission for a specific feature to behave as expected, and what
    // features are disabled if it's declined. In this UI, include a
    // "cancel" or "no thanks" button that lets the user continue
    // using your app without granting the permission.
    showInContextUI(...);
} else {
    // You can directly ask for the permission.
    requestPermissions(CONTEXT,
            new String[] { Manifest.permission.REQUESTED_PERMISSION },
            REQUEST_CODE);
}

And this code snippet demonstrates the recommended process to check for a permission and to request a permission from the user when necessary:

Kotlin
when {
    ContextCompat.checkSelfPermission(
            CONTEXT,
            Manifest.permission.REQUESTED_PERMISSION
            ) == PackageManager.PERMISSION_GRANTED -> {
        // You can use the API that requires the permission.
    }
    ActivityCompat.shouldShowRequestPermissionRationale(
            this, Manifest.permission.REQUESTED_PERMISSION) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected, and what
        // features are disabled if it's declined. In this UI, include a
        // "cancel" or "no thanks" button that lets the user continue
        // using your app without granting the permission.
        showInContextUI(...)
    }
    else -> {
        // You can directly ask for the permission.
        // The registered ActivityResultCallback gets the result of this request.
        requestPermissionLauncher.launch(
                Manifest.permission.REQUESTED_PERMISSION)
    }
}
Java
if (ContextCompat.checkSelfPermission(
        CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
        PackageManager.PERMISSION_GRANTED) {
    // You can use the API that requires the permission.
    performAction(...);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
        this, Manifest.permission.REQUESTED_PERMISSION)) {
    // In an educational UI, explain to the user why your app requires this
    // permission for a specific feature to behave as expected, and what
    // features are disabled if it's declined. In this UI, include a
    // "cancel" or "no thanks" button that lets the user continue
    // using your app without granting the permission.
    showInContextUI(...);
} else {
    // You can directly ask for the permission.
    // The registered ActivityResultCallback gets the result of this request.
    requestPermissionLauncher.launch(
            Manifest.permission.REQUESTED_PERMISSION);
}
Manage the permission request code yourself

As an alternative to allowing the system to manage the permission request code, you can manage the permission request code yourself. To do so, include the request code in a call to requestPermissions().

[!NOTE] Note: Your app cannot customize the dialog that appears when you call requestPermissions(). The text in the system permission dialog references a permission group, but this permission grouping is designed for system ease-of-use. Your app must not rely on permissions being within or outside of a specific permission group.

The following code snippet demonstrates how to request a permission using a request code:

Kotlin
when {
    ContextCompat.checkSelfPermission(
            CONTEXT,
            Manifest.permission.REQUESTED_PERMISSION
            ) == PackageManager.PERMISSION_GRANTED -> {
        // You can use the API that requires the permission.
        performAction(...)
    }
    ActivityCompat.shouldShowRequestPermissionRationale(
            this, Manifest.permission.REQUESTED_PERMISSION) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected, and what
        // features are disabled if it's declined. In this UI, include a
        // "cancel" or "no thanks" button that lets the user continue
        // using your app without granting the permission.
        showInContextUI(...)
    }
    else -> {
        // You can directly ask for the permission.
        requestPermissions(CONTEXT,
                arrayOf(Manifest.permission.REQUESTED_PERMISSION),
                REQUEST_CODE)
    }
}
Java
if (ContextCompat.checkSelfPermission(
        CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
        PackageManager.PERMISSION_GRANTED) {
    // You can use the API that requires the permission.
    performAction(...);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
        this, Manifest.permission.REQUESTED_PERMISSION)) {
    // In an educational UI, explain to the user why your app requires this
    // permission for a specific feature to behave as expected, and what
    // features are disabled if it's declined. In this UI, include a
    // "cancel" or "no thanks" button that lets the user continue
    // using your app without granting the permission.
    showInContextUI(...);
} else {
    // You can directly ask for the permission.
    requestPermissions(CONTEXT,
            new String[] { Manifest.permission.REQUESTED_PERMISSION },
            REQUEST_CODE);
}

After the user responds to the system permissions dialog, the system then invokes your app's implementation of onRequestPermissionsResult(). The system passes in the user response to the permission dialog, as well as the request code that you defined, as shown in the following code snippet:

Kotlin
override fun onRequestPermissionsResult(requestCode: Int,
        permissions: Array<String>, grantResults: IntArray) {
    when (requestCode) {
        PERMISSION_REQUEST_CODE -> {
            // If request is cancelled, the result arrays are empty.
            if ((grantResults.isNotEmpty() &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                // Permission is granted. Continue the action or workflow
                // in your app.
            } else {
                // Explain to the user that the feature is unavailable because
                // the feature requires a permission that the user has denied.
                // At the same time, respect the user's decision. Don't link to
                // system settings in an effort to convince the user to change
                // their decision.
            }
            return
        }

        // Add other 'when' lines to check for other
        // permissions this app might request.
        else -> {
            // Ignore all other requests.
        }
    }
}
Java
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission is granted. Continue the action or workflow
                // in your app.
            }  else {
                // Explain to the user that the feature is unavailable because
                // the feature requires a permission that the user has denied.
                // At the same time, respect the user's decision. Don't link to
                // system settings in an effort to convince the user to change
                // their decision.
            }
            return;
        }
        // Other 'case' lines to check for other
        // permissions this app might request.
    }
}
Request location permissions

When you request location permissions, follow the same best practices as for any other runtime permission. One important difference when it comes to location permissions is that the system includes multiple permissions related to location. Which permissions you request, and how you request them, depend on the location requirements for your app's use case.

Foreground location

If your app contains a feature that shares or receives location information only once, or for a defined amount of time, then that feature requires foreground location access. Some examples include the following:

  • Within a navigation app, a feature lets users get turn-by-turn directions.

  • Within a messaging app, a feature lets users share their current location with another user.

The system considers your app to be using foreground location if a feature of your app accesses the device's current location in one of the following situations:

  • An activity that belongs to your app is visible.

  • Your app is running a foreground service. When a foreground service is running, the system raises user awareness by showing a persistent notification. Your app retains access when it is placed in the background, such as when the user presses the Home button on their device or turns their device's display off.

    On Android 10 (API level 29) and higher, you must declare a foreground service type of location, as shown in the following code snippet. On earlier versions of Android, we recommend that you declare this foreground service type.

    <!-- Recommended for Android 9 (API level 28) and lower. -->
    <!-- Required for Android 10 (API level 29) and higher. -->
    <service
        android:name="MyNavigationService"
        android:foregroundServiceType="location" ... >
        <!-- Any inner elements go here. -->
    </service>

You declare a need for foreground location when your app requests either the ACCESS_COARSE_LOCATION permission or the ACCESS_FINE_LOCATION permission, as shown in the following snippet:

<manifest ... >
  <!-- Include this permission any time your app needs location information. -->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <!-- Include only if your app benefits from precise location access. -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
Background location

An app requires background location access if a feature within the app constantly shares location with other users or uses the Geofencing API. Several examples include the following:

  • Within a family location sharing app, a feature lets users continuously share location with family members.

  • Within an IoT app, a feature lets users configure their home devices such that they turn off when the user leaves their home and turn back on when the user returns home.

The system considers your app to be using background location if it accesses the device's current location in any situation other than the ones described in the foreground location section. The background location accuracy is the same as the foreground location accuracy, which depends on the location permissions that your app declares.

On Android 10 (API level 29) and higher, you must declare the ACCESS_BACKGROUND_LOCATION permission in your app's manifest to request background location access at runtime. On earlier versions of Android, when your app receives foreground location access, it automatically receives background location access as well.

<manifest ... >
  <!-- Required only when requesting background location access on
       Android 10 (API level 29) and higher. -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

[!NOTE] Note: The Google Play Store has a location policy concerning device location, restricting background location access to apps that need it for their core functionality and meet related policy requirements.

Handle permission denial

If the user denies a permission request, your app must help them understand the implications of denying the permission. In particular, your app must make users aware of the features that don't work because of the missing permission. When you do so, keep the following best practices in mind:

  • Minimize functionality loss. Users should be able to access the app to whatever extent is possible without the requested permissions. If functionality works at least to some extent with the data available, even if it is degraded compared to what would have been possible with the permission, keeping it on is the best option.

  • Guide the user's attention. Highlight a specific part of your app's UI where there is limited functionality because your app doesn't have the necessary permission. Examples of what you can do include the following:

    • Show a message where the feature's results or data would have appeared.

    • Display a different button that contains an error icon and color.

  • Be specific. Don't display a generic message. Instead, make clear which features are unavailable because your app doesn't have the necessary permission.

  • Don't block the user interface. In other words, don't display a full-screen warning message that prevents users from continuing to use your app at all.

  • Accept the user's preference: While it is okay to communicate to users how their functionality is being impacted by lack of access to the requested permissions, they should not feel pressured to change their mind. Letting a user know that they aren't getting full feature access when it is appropriate (such as when attempting to use a part of the app blocked without the permission) is important, but persistent nagging to reconsider is not respectful of their choice.

Following these guidelines can help ensure that users feel empowered to manage access to their private data in a way that aligns with their values. Failing to gracefully accept highly visible user privacy decisions damages the trust and reputation not only of your app, but of the ecosystem as a whole.

[!TIP] Tip: We recommend that your app encourages the best user experience possible, even after permission denials. For example, if microphone access is denied, you must still promote full usability of text functionality.

Additionally, if the user taps Deny for a specific permission more than once during your app's lifetime of installation on a device, the user will no longer see the system permissions dialog if your app requests that permission again. The user's action implies "don't ask again," and is considered a permanent denial. It is very important to only prompt users for permissions when they need access to a specific feature; otherwise, you might inadvertently lose the ability to re-request permissions. In certain situations, the permission might be denied automatically, without the user taking any action. (A permission might be granted automatically as well.) It is important to not assume anything about automatic behavior. Each time your app needs to access functionality that requires a permission, check that your app is still granted that permission. To provide the best user experience when asking for app permissions, also see App permissions best practices.

Inspect denial status when testing and debugging

To identify whether an app has been permanently denied permissions (for debugging and testing purposes), use the following command:

adb shell dumpsys package PACKAGE_NAME

Where <var translate="no">PACKAGE_NAME</var> is the name of the package to inspect.

The output of the command contains sections that look like this:

...
runtime permissions:
  android.permission.POST_NOTIFICATIONS: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
  android.permission.ACCESS_FINE_LOCATION: granted=false, flags=[ USER_SET|USER_FIXED|USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
  android.permission.BLUETOOTH_CONNECT: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
...

Permissions that have been denied once by the user are flagged by USER_SET. Permissions that have been denied permanently by selecting Deny twice are flagged by USER_FIXED.

To make sure that testers see the request dialog during testing, reset these flags when you're done debugging your app. To do this, use the command:

adb shell pm clear-permission-flags PACKAGE_NAME PERMISSION_NAME user-set user-fixed

<var translate="no">PERMISSION_NAME</var> is the name of the permission you want to reset.

PERMISSION_NAME is the name of the permission you want to reset.

To view a complete list of Android app permissions, visit the permissions API reference page.

One-time permissions

Image: The option called 'Only this time' is the second of three buttons in the dialog. Figure 2. System dialog that appears when an app requests a one-time permission.

Starting in Android 11 (API level 30), whenever your app requests a permission related to location, microphone, or camera, the user-facing permissions dialog contains an option called Only this time , as shown in figure 2. If the user selects this option in the dialog, your app is granted a temporary one-time permission.

Your app can then access the related data for a period of time that depends on your app's behavior and the user's actions:

  • While your app's activity is visible, your app can access the data.

  • If the user sends your app to the background, your app can continue to access the data for a short period of time.

  • If you launch a foreground service while the activity is visible, and the user then moves your app to the background, your app can continue to access the data until the foreground service stops.

App process terminates when permission revoked

If the user revokes the one-time permission, such as in system settings, your app cannot access the data, regardless of whether you launched a foreground service. As with any permission, if the user revokes your app's one-time permission, your app's process terminates.

When the user next opens your app and a feature in your app requests access to location, microphone, or camera, the user is prompted for the permission again.

[!NOTE] Note: If your app already follows best practices when it requests runtime permissions, you don't need to add or change any logic in your app to support one-time permissions.

Reset unused permissions

Android provides several ways to reset unused runtime permissions to their default, denied state:

Remove app access

On Android 13 (API level 33) and higher, you can remove your app's access to runtime permissions that your app no longer requires. When you update your app, perform this step so that users are more likely to understand why your app continues to request specific permissions. This knowledge helps build user trust in your app.

To remove access to a runtime permission, pass the name of that permission into revokeSelfPermissionOnKill(). To remove access to a group of runtime permissions at the same time, pass a collection of permission names into revokeSelfPermissionsOnKill(). The permission removal process happens asynchronously and kills all processes associated with your app's UID.

[!NOTE] Note: For system settings to show that your app doesn't access data in a particular permission group, you must remove access to all permissions in that permission group. In this case, it can be helpful to call revokeSelfPermissionsOnKill() and pass in multiple permissions within the permission group.

For the system to remove your app's access to the permissions, all processes tied to your app must be killed. When you call the API, the system determines when it is safe to kill these processes. Usually, the system waits until your app spends an extended period of time running in the background instead of the foreground.

To inform the user that your app no longer requires access to specific runtime permissions, show a dialog the next time the user launches your app. This dialog can include the list of permissions.

Auto-reset permissions of unused apps

If your app targets Android 11 (API level 30) or higher and is not used for a few months, the system protects user data by automatically resetting the sensitive runtime permissions that the user had granted your app. Learn more in the guide about app hibernation.

Request to become the default handler if necessary

Some apps depend on access to sensitive user information related to call logs and SMS messages. If you want to request the permissions specific to call logs and SMS messages and publish your app to the Play Store, you must prompt the user to set your app as the default handler for a core system function before requesting these runtime permissions.

For more information on default handlers, including guidance on showing a default handler prompt to users, see the guide about permissions used only in default handlers.

Grant all runtime permissions for testing purposes

To grant all runtime permissions automatically when you install an app on an emulator or test device, use the -g option for the adb shell install command, as demonstrated in the following code snippet:

adb shell install -g PATH_TO_APK_FILE

Additional resources

For additional information about permissions, read these articles:

To learn more about requesting permissions, review the permissions samples.

You can also complete this codelab that demonstrates privacy best practices.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Google LLC
Publisher
Android (Google) (Official)
Distributed by
Android (Google)
Source
android/skills · security/android-permissions-security · commit bf68e2d
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.txt)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download android-permissions-security.zip