Photo picker, partial photo permissions, per-app language, package visibility, scoped storage, and runtime permission rationales. Use when developing Android apps with android privacy.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill android-privacy-expert --agent claude-codeInstalls 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 automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-android-privacy-expert)More formats (shields.io, HTML) on the badges page.
---
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)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!