Irbisa · Cheatsheet

Permissions & Privacy

Runtime permissions, purpose strings, ATT, location, photos, privacy manifests, consent, SDK audit

Middle Developer · 19 questions

  1. 01

    How does requesting a permission differ between Android and iOS?

    On Android you declare the permission in the manifest and request it yourself at runtime; iOS has no generic request call: the system prompts on first use of the API and kills the app if the purpose string is missing from Info.plist.

  2. 02

    What do "Only this time" and "Approximate" in the Android permission dialog mean for your code?

    Since Android 11 the user can grant a one-time permission that is revoked soon after the app leaves the screen, and since Android 12 a location request can come back as coarse only.

  3. 03

    Onboarding asks for location, contacts and notifications on the first screen, and most users deny all three. When should you ask instead?

    Ask at the moment the user starts the feature that needs the permission, and show your own short explanation first.

    @Composablefun AddToCalendarButton(vm: BookingViewModel) {    val context = LocalContext.current    var showPrimer by remember { mutableStateOf(false) }    val launcher = rememberLauncherForActivityResult(        ActivityResultContracts.RequestPermission()…
  4. 04

    The app crashes when the user taps Scan. What is wrong with this permission request?

    class ScanFragment : Fragment(R.layout.fragment_scan) {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        view.findViewById<Button>(R.id.scan).setOnClickListener {            val request = registerForActivityResult(                ActivityResultContracts.RequestPermission()            ) { granted ->                if (granted) startScanner()            }            request.launch(Manifest.permission.CAMERA)        }    }}

    registerForActivityResult is called inside the click listener, after the fragment is already created, so it throws IllegalStateException: "Fragments must call registerForActivityResult() before they are created".

  5. 05

    shouldShowRequestPermissionRationale returns false both on a fresh install and after the user blocked the permission. How do you tell these states apart?

    Not from that call alone: it returns true only after a denial the user can still reverse, and false both before the first request and after a permanent denial.

    class PermissionGate(private val prefs: DataStore<Preferences>) {    private fun blockedKey(permission: String) = booleanPreferencesKey("blocked_$permission")     suspend fun state(activity: Activity, permission: String): PermissionState = when {        ContextCompat.checkSelfPermission(activity, permission) ==            PackageManager.PERMISSION_GRANTED -> PermissionState.Granted…
  6. 06

    Which features can you ship without requesting a dangerous permission at all, by letting a system component do the work?

    Many: when a system UI acts on the user's explicit choice, your app receives only the result (one photo, one contact, one SMS) and never needs the broad permission.

    // Photo: the camera app takes it and writes to your FileProvider Uri. No CAMERA permission.private val takePhoto = registerForActivityResult(ActivityResultContracts.TakePicture()) { saved ->    if (saved) upload(photoUri)}// takePhoto.launch(photoUri)…
  7. 07

    What is the Android Photo Picker and partial media access in Android 14, and why prefer the picker over READ_MEDIA_IMAGES?

    The Photo Picker is a system UI that returns only the items the user picks, with no permission at all.

    // Attaching an avatar: no permission, no manifest entry, nothing to denyval pickAvatar = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->    if (uri != null) upload(uri)}fun chooseAvatar() = pickAvatar.launch(    PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)…
  8. 08

    Find the bug: the avatar the user picked shows fine, but after the app is restarted it is gone and the log shows an exception.

    class ProfileFragment : Fragment(R.layout.fragment_profile) {    private val prefs by lazy { requireContext().getSharedPreferences("profile", Context.MODE_PRIVATE) }     private val pickAvatar = registerForActivityResult(PickVisualMedia()) { uri ->        if (uri == null) return@registerForActivityResult        prefs.edit { putString("avatar_uri", uri.toString()) }        showAvatar(uri) // reads it with contentResolver.openInputStream(uri)    }     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        prefs.getString("avatar_uri", null)?.let { showAvatar(it.toUri()) }        view.findViewById<Button>(R.id.change_avatar).setOnClickListener {            pickAvatar.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))        }    }}

    The app saves the picker's content:// URI but not the right to read it.

  9. 09

    A courier app needs the driver's location while the screen is off during a delivery. Which permissions do you request, and do you need background location at all?

    Request fine and coarse location while the app is visible and run the trip in a foreground service of type location; background location is usually not needed.

    private val requestLocation = registerForActivityResult(    ActivityResultContracts.RequestMultiplePermissions()) { result ->    when {        result[Manifest.permission.ACCESS_FINE_LOCATION] == true -> startTrip(precise = true)        result[Manifest.permission.ACCESS_COARSE_LOCATION] == true -> startTrip(precise = false)…
  10. 10

    After raising targetSdk this always shows "No maps app installed", even with Google Maps on the phone. Why, and how do you fix it?

    fun openInMaps(context: Context, lat: Double, lng: Double) {    val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:$lat,$lng"))    if (intent.resolveActivity(context.packageManager) != null) {        context.startActivity(intent)    } else {        Toast.makeText(context, "No maps app installed", Toast.LENGTH_SHORT).show()    }}

    Package visibility: since Android 11 (target 30+) an app sees only a filtered list of other installed apps, so resolveActivity returns null for an app it is not allowed to see.

  11. 11

    What do the Android 12 camera and microphone indicators and toggles change for your app?

    A green dot appears whenever your app uses the camera or microphone, and the user can switch either sensor off for the whole device.

  12. 12

    A minor SDK update adds READ_PHONE_STATE to your release manifest. How do you find where it came from, and what are your options?

    Library manifests are merged into yours, so any dependency can add a <uses-permission> nobody on the team wrote.

    // build.gradle.kts (:app): run ./gradlew checkReleasePermissions in CI// import com.android.build.api.artifact.SingleArtifactabstract class CheckPermissions : DefaultTask() {    @get:InputFile abstract val mergedManifest: RegularFileProperty    @get:InputFile abstract val allowList: RegularFileProperty…
  13. 13

    What does the Play Data safety form require, and how does a third-party SDK change your answers?

    It is your public declaration of what data the app collects and shares, why, and how it is protected, and it covers everything your SDKs send: Play treats their traffic as yours.

  14. 14

    What is App Tracking Transparency, and what happens to the IDFA and ad attribution when the user declines?

    ATT is the iOS prompt an app must show before tracking (linking its data with other companies' data for ads, or passing it to data brokers).

    import AppTrackingTransparencyimport AdSupport // Call when the first screen is visible, e.g. when scenePhase becomes .active.// Called from didFinishLaunching, the alert is never shown.@MainActor…
  15. 15

    Your iOS upload is rejected with ITMS-91053 for an SDK you did not write. What does the privacy manifest declare, and what is Apple checking?

    PrivacyInfo.xcprivacy is a property list where the app and each SDK declare tracking, collected data types and a reason for every "required reason" API they call.

    <plist version="1.0"><dict>  <key>NSPrivacyTracking</key>  <false/>  <key>NSPrivacyTrackingDomains</key>  <array/>…
  16. 16

    Every analytics call sits behind a consent flag, yet the vendor is contacted before the consent dialog is answered. Where does that request come from?

    From auto-initialisation: Firebase and many SDKs start through a ContentProvider or an androidx.startup initializer that runs before Application.onCreate, so they collect before your flag is ever read.

    // AndroidManifest.xml: read before any of your code runs// <meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />// <meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />// <meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="false" /> data class Consent(val analytics: Boolean, val ads: Boolean, val crashes: Boolean)…
  17. 17

    Product wants to open the app to children under 13. What changes in the store declarations, the ad and analytics stack, and the data you may collect?

    A child audience switches most third-party telemetry off: no advertising ID, no personalised ads, only SDKs certified for families, and verifiable parental consent before collecting personal data.

    enum class Audience { CHILD, TEEN, ADULT } // Applied once, before MobileAds.initialize()// AndroidManifest.xml: <meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />fun configureForAudience(app: Application, audience: Audience) {    val child = audience == Audience.CHILD…
  18. 18

    A user taps "Delete my account". What has to happen beyond deleting the user row, and what do you promise about backups?

    Both stores require deletion to start inside the app, and the row is the easy part: the server fans the deletion out to every system and vendor holding the user's data, the device is wiped, and backups are purged on a stated retention schedule.

  19. 19

    A reviewer asks you to prove the app sends only what your Data safety form claims. What evidence can you produce?

    Three things: a data inventory reviewed like code, a traffic capture from a fresh install on a real device, and a CI check that fails when the app contacts a host the inventory does not list.