Homelab the Right Way

Home › Posts

The permission Play rejected isn't in your manifest

Google Play reviews the merged manifest, not the one you wrote. Here is how to see what your release build really asks for, strip what a library added, and replace media permissions with the photo picker.

· 4 min read · androidgoogle playpermissionsplay consolephoto picker

The permission Play rejected isn't in your manifest

Play Console flags a restricted permission on your update. You open AndroidManifest.xml, search for it, and find nothing. You never added it. It is still in the build.

That is the most common way a permission problem catches people. This post walks through why it happens and how to fix it for good, using Chapter 4 (Sensitive Permissions) of Pass Google Play Review.

The trap: you are reading the wrong manifest

Google Play treats some permissions as restricted. You may only ship them when a simpler, narrower option cannot do the job, and several need a declaration in Play Console before an update can be sent.

The surprise is rarely a permission you added. It is one a library merged in. The final manifest is a merge of yours and every dependency's. Libraries contribute permissions, services and receivers. The merged result is what ships, and it is what Play reviews. So searching your own source file and finding nothing alarming proves very little.

Step 1: look at what you actually ship

Check the permissions in the built release, not the source:

# APK: list permissions from the merged manifest
apkanalyzer manifest permissions app/build/outputs/apk/release/app-release.apk

In Android Studio you can also open your manifest and switch to the Merged Manifest tab. It shows each entry and which library contributed it. That tells you whether a permission is yours or came along with a dependency.

Step 2: remove what a library added and you don't need

tools:node="remove" takes an unwanted entry out of the merged result:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"
        tools:node="remove" />
</manifest>

Run apkanalyzer on the new release build afterwards and confirm the permission is gone from the list.

Step 3: pick the narrower option first

For every restricted permission, the book's rule is to ask in a fixed order. Most features have a narrower option that needs no permission and no declaration.

PermissionNarrower option firstIf you really need it
READ_MEDIA_IMAGES, READ_MEDIA_VIDEOPhoto pickerCore broad-access use only (gallery-type apps), declaration
MANAGE_EXTERNAL_STORAGEStorage Access Framework, MediaStoreDeclaration; core need only
ACCESS_BACKGROUND_LOCATIONForeground location while in useDeclaration form, video, prominent disclosure, privacy policy
USE_EXACT_ALARMInexact alarms, WorkManager, SCHEDULE_EXACT_ALARMCalendar and alarm clock apps only

Photos and videos: the picker needs no permission

Play Console Help ("Understanding Restricted Permissions with minimum scope alternatives", checked October 2026) says apps targeting Android 13 or later may only request READ_MEDIA_IMAGES and READ_MEDIA_VIDEO if system pickers like the Android Photo Picker are not sufficient for core functionality. Full compliance became mandatory on May 28, 2025.

Gallery-type apps that manage all of a user's photos qualify. An app that lets the user choose a picture to edit, attach or upload does not. A custom picker does not automatically qualify either.

The photo picker needs no permission at all. With androidx.activity 1.7.0 or higher, in Compose:

@Composable
fun AvatarPicker(onPicked: (Uri) -> Unit) {
    val pickMedia = rememberLauncherForActivityResult(
        ActivityResultContracts.PickVisualMedia()
    ) { uri ->
        if (uri != null) onPicked(uri)   // null when the user backs out
    }
    Button(onClick = {
        pickMedia.launch(
            PickVisualMediaRequest(
                ActivityResultContracts.PickVisualMedia.ImageOnly
            )
        )
    }) { Text("Choose photo") }
}

A few details from the chapter:

  • On devices without the picker, the library falls back to ACTION_OPEN_DOCUMENT on its own.
  • Use PickMultipleVisualMedia(maxItems) for several items.
  • By default, access to a picked item lasts until the device restarts or the app stops. If you upload in the background, persist it:
context.contentResolver.takePersistableUriPermission(
    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)

The picker grants access to exactly what the user selects and nothing else. There is no permission prompt, no declaration, and nothing to justify in Play Console.

A case note from a real app

The book includes a case note from the author's own app, CaptionBurn (com.charlesh.captionburn). Play Console refused to send a CaptionBurn update with the issue "Use alternative system pickers for photos / videos". The live bundle requested READ_MEDIA_VIDEO.

The app already chose videos with PickVisualMedia, so the permission had no job. The fix removed it from the build. The current manifest in chartmann1590/captionburn strips every media and storage permission a library might merge in, with tools:node="remove":

  • READ_MEDIA_IMAGES
  • READ_MEDIA_VIDEO
  • READ_MEDIA_VISUAL_USER_SELECTED
  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE

One more detail worth knowing: the Photo and video permissions declaration page kept listing the old permission from the live bundle until the new release went live. Seeing it there after the fix does not mean the fix failed.

Checklist before you send an update

  • Read the permission list from the release build with apkanalyzer manifest permissions, not from your source manifest.
  • Check the Merged Manifest tab to see which library added each entry.
  • Remove unused library permissions with tools:node="remove".
  • Choose media with the photo picker. No READ_MEDIA_* unless the app is gallery-type.
  • Persist picked URIs with takePersistableUriPermission if you upload in the background.
  • For every other restricted permission, try the narrower option in the table first.
  • After the fix ships, expect the declaration page to show the old permission until the new release is live.

More articles