Permissions that still cause Play policy review — and how to read them from an APK in the browser

Play does not fail you for INTERNET. It fails you for leftover READ_SMS, Call Log, or All files access that a library merged in. Here is the desk pass we run before the console form.

What this article is for

You have a release APK or a merged AndroidManifest.xml and a Play upload this week. You want a readable permission list, protection levels, and a short list of names that still trigger restricted permission review. You do not want to open Android Studio for that pass.

You will use the APK Analyzer only to confirm you have a ZIP with a manifest, then get text permission names (source, apktool, or aapt dump badging) and paste them into the Permissions Decoder. The decoder is a curated local list. It is not Play Console and it does not see your Data safety answers.

Step 1 — Do not paste binary AXML

The packaged AndroidManifest.xml is binary. Dropping the APK on the decoder does nothing useful. The analyzer can hint at a package name from printable strings; it does not emit a clean uses-permission list. If you only have the APK, decode with apktool or run:

aapt dump permissions your.apk
aapt dump badging your.apk

Copy every uses-permission: line. That text is what the decoder understands. Merge from Gradle can add names that never appear in app/src/main/AndroidManifest.xml. If the aapt dump is longer than your source file, that is the story — a library, not a mysterious Play policy.

Worked example — flashlight leftovers

Paste this into the decoder (we have seen this shape from an old analytics + SMS-retriever sample that nobody removed):

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

INTERNET is normal. Users never see a dialog. CAMERA is dangerous — you need a runtime prompt and a feature that actually uses the camera. POST_NOTIFICATIONS is dangerous on API 33+. READ_SMS is the line that still sends a flashlight or notes app into Play’s SMS/Call Log policy. If you are not the default SMS handler, remove it from the merge and ship again. “The decoder said dangerous” is not permission to keep it.

Second case we still see: a notes app with READ_CALL_LOG because a crash-reporting sample copied a telephony demo. Same outcome. Restricted groups are not “dangerous plus a warning.” Play wants a declaration, a default-handler story, or removal.

Names that still burn an afternoon

  • READ_SMS, RECEIVE_SMS, SEND_SMS, READ_CALL_LOG, PROCESS_OUTGOING_CALLS — SMS and Call Log policy. Default handler or remove.
  • MANAGE_EXTERNAL_STORAGE (All files access) — special, not the old storage permission. Play wants a core use case (file manager, backup) or you drop to scoped storage.
  • REQUEST_INSTALL_PACKAGES — not for a normal store listing. Sideload/updater apps only, with a documented flow.
  • ACCESS_BACKGROUND_LOCATION — separate from coarse/fine. A weather tile does not justify it.
  • QUERY_ALL_PACKAGES — Play treats this as a visibility grab. Declare the specific packages you query instead.

The decoder will label some of these special or dangerous. Treat that as a stop sign, then read the current Play policy page for the name. Policy text changes; our list is a teaching map of about forty platform constants, not PackageManager for every API.

What the decoder will mark unknown

OEM names (com.huawei.*, com.samsung.*), custom permissions you declare, and typos do not fuzzy-match. Unknown is a cue to read the library README, not a green light. com.google.android.c2dm.permission.RECEIVE is historic; FCM apps still show related names. Do not delete FCM permissions because they look “non-platform.”

Protection levels have moved with targetSdk. WRITE_EXTERNAL_STORAGE on target 32 is not the same conversation as on target 28. Check docs for the SDK you ship, not a blog from 2019.

What Play still asks that this page cannot see

  • Whether the APK uses the API, not only declares the name. Unused leftovers still fail policy.
  • Data safety form answers and the privacy policy URL.
  • Whether you filled the restricted-permission declaration in Play Console.
  • Video or screenshots that prove the SMS / All-files use case.

If those are empty, a clean decoder report will not save the review. Pair this pass with permissions users actually notice so you also trim dialogs users remember.

Screenshot / evidence to keep in the release ticket

Keep three artifacts: the decoder report (copy button), the aapt permissions dump, and a one-line note for each dangerous/special name (“needed for X” or “removed in commit abc”). Next month’s you will not remember why Call Log was there. If Gradle merge added the name, paste the dependency line in the same comment.

When to leave the browser

Merged-manifest surprises from Gradle belong in Android Studio’s Merged Manifest view. Policy text belongs on support.google.com, not in our FAQ. Run ./gradlew :app:dependencies when a name has no owner in your source. The APK analysis guide is the rest of the preflight (versions, exported, signing). Job table: browser vs apktool.