Play reads your manifest before your users do
Every permission line in an Android manifest is a promise Google Play reviews before a user sees the app, with less context than the user has. The account carries the risk.
An Android manifest is read twice before anyone uses the app. The device reads it at install, to know what the app may ask for. Google Play reads it before that, at review, to decide whether the app may be listed at all, and the reviewer has less context than the user will: no onboarding screen, no explanation, just a list of permissions and a policy that says which ones need justifying. When eight apps ship under one developer account, as mine do, a permission that looks unjustified in one app is a strike against the account that publishes all eight. The habit that keeps the account clean is boring and effective: a written reason for every permission, a screen where it is asked, and a path through the app when it is refused, kept in one table that the release process reads before the reviewer does.
The policy has been tightening for years
Play's permission policy has moved in one direction, and each move has the same shape: a permission group that used to be free to request becomes one that must be justified or replaced. The permissions policy restricts the SMS and call log groups to apps whose core purpose needs them, treats the inventory of installed apps as personal data so that broad package visibility is allowed only for apps like launchers, file managers and security tools, and requires high-risk permissions to be declared through a form in the console. The photo and video policy says that apps targeting Android 13 or later may request the broad media permissions only when the system photo picker is not sufficient, which for one-time or occasional access it always is, with full compliance mandatory for every developer from 28 May 2025 and non-compliant apps subject to removal after that date.
The direction is the point. A permission that is acceptable today is a candidate for the next restriction, and an app whose manifest carries permissions it does not need is an app that will fail a future review for a reason nobody remembers adding. The photo picker rule caught a great many apps that had requested storage access years earlier for a feature since removed, and the fix in each case was not engineering but archaeology: finding out why the line was there.
The permission ledger
The archaeology is what the ledger prevents. It is a table with one row per manifest permission and five columns: the user-visible reason, in the words the app will show; the screen on which the permission is requested, which must be the screen where the feature that needs it is used; the fallback when the user refuses, which must be a working path through the app and not a dead end; the policy section the permission falls under; and the date and release in which it was added. A permission without a complete row does not ship. A row whose reason no longer describes a feature in the app is a permission to remove.
The three middle columns do work that the policy text asks for in different words. The reason column is what the permissions declaration form wants, written once and reused. The screen column enforces the rule that permissions are requested in context, at the moment the feature needs them and not at first launch, which is both policy and the single largest factor in whether users grant them. The fallback column is the one most teams skip and the one the photo policy makes explicit: an app must make a reasonable effort to work for a user who declines, and a feature that dead-ends on refusal is a feature that will be described in a review as coercive.
The review path, with the ledger feeding it
The ledger is not documentation for its own sake; it feeds the release process at the points where Play asks questions. The console's data safety section and the permissions declaration form are both filled in from the ledger's columns, so that the answers Play sees are the same answers the app shows users, which is the consistency a reviewer is checking for. A pull request that adds a manifest permission fails a check unless it also adds a ledger row, and a release checklist step diffs the manifest against the ledger so that a permission added by a dependency, which happens more often than people expect, is caught before upload rather than by the reviewer.
Two permissions that look free and are not
Two lines in particular deserve rows because they are so easily added without thought. The first is the internet permission, which nearly every template includes and which a device-only app does not need; its presence in an app that promises not to send data anywhere is a contradiction a reviewer can see in the manifest and a user can see in the store listing, and removing it is the cheapest credibility an offline app can buy. The second is the exact alarm permission, which reminder features reach for by habit and which Android 12 and later treat as a user-revocable grant; its row needs a fallback, an inexact alarm within a window, because a user who revokes it must still get their reminder, late by a few minutes rather than never.
The ledger also has to say who owns each row, because permissions arrive from libraries as often as from feature work. An analytics or advertising dependency can add a permission through its own manifest that merges into the app's, and the first the team hears of it is a reviewer's question. The diff in continuous integration is what catches that, and the row it demands is what forces the decision: keep the dependency and justify the permission, or drop the dependency. There is no third option, and pretending there is one is how manifests grow lines nobody can explain.
Why the account is the unit of risk
Google Play's enforcement is applied to developer accounts, and the practical meaning of that for a small studio is that every app is a liability for every other. A permissions problem in the least-used of eight apps is not contained to that app; repeated or serious issues escalate to the account, and an account under enforcement cannot publish updates to any of its apps. That is the asymmetry the ledger is built for. The cost of a row is ten minutes per permission. The cost of a missing one is, in the worst case, every release of every app stalled while an appeal is written about a line in a manifest that nobody remembers adding.
It also changes what "minimal permissions" means. For a single app, minimal is a nice principle. For an account with several, minimal is a control on shared risk, and the ledger is the mechanism: fewer rows means fewer things to justify, fewer surfaces for a future policy to catch, and fewer questions from a reviewer who has only the manifest to go on. The manifest is the first thing Play reads and the last thing most developers look at. Reading it first, with a ledger, is how the eight apps keep publishing.
Get new posts by email
Occasional essays on engineering, AI, and building for the people technology leaves behind.
Subscribe with RSS