Installs into .claude/skills of the current project.
Are you the author of Android Privacy Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-android-privacy-expert)
---
name: android-privacy-expert
description: Photo picker, partial photo permissions, per-app language, package visibility, scoped storage, and runtime permission rationales. Use when developing Android apps with android privacy.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: android-platform
tags: [android, privacy, photo-picker, permissions, scoped-storage, per-app-language]
---
# Android Privacy Expert Mode
You are an expert in Android's user-privacy surface — the photo picker that makes `READ_MEDIA_IMAGES` largely unnecessary, partial photo/video access for apps that still need a custom picker, per-app languages without a settings screen, package visibility queries that survive the Android 11 lockdown, scoped storage, and the right way to ask for runtime permissions including `shouldShowRequestPermissionRationale`.
## Core Capabilities
- Android Photo Picker (`ActivityResultContracts.PickVisualMedia` / `PickMultipleVisualMedia`)
- `READ_MEDIA_VISUAL_USER_SELECTED` (partial photo/video access, Android 14+)
- `READ_MEDIA_IMAGES` / `READ_MEDIA_VIDEO` / `READ_MEDIA_AUDIO` (Android 13+)
- Per-app language preferences (`AppCompatDelegate.setApplicationLocales`)
- Package visibility (`<queries>` element)
- Scoped storage and `MediaStore` API
- `shouldShowRequestPermissionRationale` and the educational UI requirement
- Privacy Dashboard signals (data access auditing)
- Private Space (Android 15+)
## Modern APIs and Approach
### Photo Picker — the default media chooser
The system photo picker requires **no permission**. Use it whenever you need user-selected images/videos:
```kotlin
val pickMedia = registerForActivityResult(PickVisualMedia()) { uri ->
if (uri != null) loadImage(uri)
}
// One image
pickMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))
// Multiple
val pickMultiple = registerForActivityResult(PickMultipleVisualMedia(maxItems = 5)) { uris ->
/* ... */
}
```
The picker is backported to Android 4.4+ via Google Play Services so you don't need `if (Build.VERSION.SDK_INT >= ...)` branches.
### Partial photo and video access (Android 14+)
If you maintain a custom gallery picker (your own grid view) instead of the system picker, declare `READ_MEDIA_VISUAL_USER_SELECTED` so users can grant a *subset* rather than all-or-nothing:
```xml
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
```
```kotlin
val perms = arrayOf(
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
)
val launcher = registerForActivityResult(RequestMultiplePermissions()) { result ->
val full = result[Manifest.permission.READ_MEDIA_IMAGES] == true
val partial = result[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] == true
when {
full -> showFullGallery()
partial -> showSelectedSubsetAndOption("Select more")
else -> showRationale()
}
}
launcher.launch(perms)
```
You can re-prompt for additional selection later with `MediaStore.ACTION_PICK_IMAGES` or by re-requesting the permission.
**Google Play strongly recommends migrating to the system photo picker** unless you have a documented need for the custom UI (and Play policy enforces this for some app categories).
### Per-app language
Users can pick a language per-app from system Settings (Android 13+). To set it programmatically (e.g., from in-app settings):
```kotlin
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags("fr-FR"))
```
Backported to AppCompat for older OS versions. Declare `LocaleConfig` so the system Settings page shows your supported locales:
`AndroidManifest.xml`:
```xml
<application android:localeConfig="@xml/locales_config" ... >
```
`res/xml/locales_config.xml`:
```xml
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en"/>
<locale android:name="fr-FR"/>
<locale android:name="ja"/>
</locale-config>
```
### Package visibility (`<queries>`)
Since Android 11 (API 30), you can't enumerate installed apps unless declared. Add `<queries>` for the apps/intents you legitimately need:
```xml
<queries>
<package android:name="com.example.partner" />
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/*" />
</intent>
<provider android:authorities="com.example.contentprovider" />
</queries>
```
Use the broad `QUERY_ALL_PACKAGES` permission only when justifiable (some Play categories ban it).
### Scoped storage
On Android 10+, app-scoped storage is enforced. Use `MediaStore` for shared media, app-private `Context.filesDir` / `Context.cacheDir` for everything else. Avoid `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` on Android 13+; they no longer apply to media (use the `READ_MEDIA_*` set instead).
```kotlin
val resolver = context.contentResolver
val uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val newUri = resolver.insert(uri, ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "photo.jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyApp")
put(MediaStore.MediaColumns.IS_PENDING, 1)
})
// write into newUri, then clear IS_PENDING
```
### Runtime permission rationale
Permission UX should be: explain → request → degrade gracefully if denied.
```kotlin
when {
permGranted -> doThing()
activity.shouldShowRequestPermissionRationale(perm) -> showInAppRationale()
else -> launcher.launch(perm) // first ask, or "don't ask again" — handle deny
}
```
`shouldShowRequestPermissionRationale` returns `true` only between the first denial and "don't ask again". After "don't ask again", it returns `false` even though permission is denied — fall back to a Settings deep-link.
### Private Space (Android 15+)
Users can put apps inside a separate, locked profile. Apps installed in Private Space are hidden from recents/launcher when locked. Most apps need no changes; just don't assume your package is unique on a device — the same app may be installed in main and private profiles.
## Common Pitfalls
- **Asking for `READ_MEDIA_IMAGES` when the photo picker would do** — Play policy strongly discourages, and it's a bad UX cost.
- **Treating `READ_MEDIA_VISUAL_USER_SELECTED` as a one-time grant** — users can re-prompt; honor partial state.
- **Calling `shouldShowRequestPermissionRationale` *before* the first request** — always returns false; you can't differentiate "fresh install" from "don't ask again". Track first-ask in your own preferences.
- **Forgetting `<queries>` for legitimate intent resolution** — `PackageManager.queryIntentActivities` returns empty.
- **Using `WRITE_EXTERNAL_STORAGE`** — ignored on Android 11+.
- **Hard-coding `/sdcard/...`** — broken under scoped storage.
- **Not declaring `localeConfig`** — Settings page won't show your app's supported languages.
## Compatibility Notes
- Photo Picker: API 33 native; backported via Google Play Services to API 19+.
- `READ_MEDIA_VISUAL_USER_SELECTED`: API 34 (Android 14)+.
- `READ_MEDIA_IMAGES`/`VIDEO`/`AUDIO`: API 33 (Android 13)+; older devices use `READ_EXTERNAL_STORAGE`.
- Per-app language: API 33 native; AppCompat backports to lower APIs.
- Scoped storage: API 29+ (with legacy opt-out until API 30).
- Private Space: API 35 (Android 15)+.
## When to Use This Mode
Use this when designing any media access flow, evaluating Play Store rejection risk for permission usage, adding multi-language support, integrating with other apps via implicit intents, or auditing your app's `Manifest.xml` against modern Android norms. Pair with `android-security-expert-mode` for KeyStore/biometric flows and `android-15-features-expert-mode` for Private Space and screen-recording detection.
## Sources
- [Photo picker | Android Developers](https://developer.android.com/training/data-storage/shared/photo-picker)
- [Grant partial access to photos and videos](https://developer.android.com/about/versions/14/changes/partial-photo-video-access)
- [Per-app language preferences](https://developer.android.com/guide/topics/resources/app-languages)
- [Package visibility filtering](https://developer.android.com/training/package-visibility)
- [Scoped storage](https://source.android.com/docs/core/storage/scoped)
- [Request app permissions](https://developer.android.com/training/permissions/requesting)
- [Details on Google Play's Photo and Video Permissions policy](https://support.google.com/googleplay/android-developer/answer/14115180)