Skip to content
Back to skills

Android Privacy Expert

ASecurity

Photo picker, partial photo permissions, per-app language, package visibility, scoped storage, and runtime permission rationales. Use when developing Android apps with android privacy.

  • 9 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 8, 2026
developmentgokotlingitapisecurity

Works with

  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill android-privacy-expert --agent claude-code

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.

Security grade badge for Android Privacy Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-android-privacy-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-android-privacy-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.