Zygote
APKs & installationintermediate

Package visibility

Before Android 11, any app could enumerate every installed app. Since API 30 it can only see what it declares an interest in — a change that broke a generation of apps and revealed how much they depended on the full list.

11 minAPI 21–37 · 17interactivequizverified 2026-10-27

getInstalledPackages() used to return everything: every app on the device, its package name, its version, its components. That list is a fingerprint of the user — what they install says a lot about who they are. Android 11 made visibility a declaration rather than a default.

TL;DR#

  • Since API 30 (Android 11), an app sees only packages it has declared an interest in through a <queries> element, plus a few implicit exceptions.
  • The change affects getInstalledPackages, queryIntentActivities, resolveActivity, and anything built on them — including some deep-link checks and share sheets.
  • The exceptions are deliberate: the app's own package, the system's core packages, the app that installed it, and packages the app already interacts with.
  • QUERY_ALL_PACKAGES exists but is a restricted permission; Play allows it only for a narrow set of use cases.
  • The fix is almost always to declare a <queries> element, not to request the broad permission.

Mental model#

Think of a building directory.

Before, the lobby had a list of every tenant, with their flat numbers and phone extensions. Anyone who walked in could photograph it.

Now the lobby shows you only the tenants you have an appointment with, plus the building's own services (reception, maintenance). If you want to visit someone, you say who in advance.

Where the analogy breaks#

You do not always know in advance who you want to visit. A share sheet is exactly that case: the app wants to show every app that can receive an image, without knowing which ones exist. The <queries> element handles this by declaring a kind of app rather than a specific package, which is closer to "show me everyone who accepts deliveries".

The big picture#

What the app doesBefore API 30API 30+
getInstalledPackages()everythingdeclared packages only
queryIntentActivities()all matching appsmatching apps it can see
resolveActivity()best matchnull if not visible
startActivity(explicit)worksworks
bindService(explicit)worksworks

The last two rows are the important ones: visibility affects discovery, not direct invocation.

Loading diagram…
Text description of this diagram

A timeline of package and install changes across four lanes: packaging, install path, what an app can see, and sideloading rules. API 1 is universal APKs. API 21 introduces PackageInstaller sessions. API 23 adds runtime permissions. API 26 makes unknown sources a per-app permission. API 28 introduces the App Bundle as an upload format. API 30 adds package visibility filtering and incremental install. API 33 adds restricted settings for sideloaded apps. API 34 adds partial media grants.

Deep dive#

What <queries> declares#

There are three ways to declare interest:

<queries>
    <!-- a specific package -->
    <package android:name="com.example.scanner" />
 
    <!-- an intent signature -->
    <intent>
        <action android:name="android.intent.action.SEND" />
        <data android:mimeType="image/*" />
    </intent>
 
    <!-- an authority -->
    <provider android:authorities="com.example.provider" />
</queries>

The <intent> form is the one that solves the share-sheet case: "I want to see every app that handles ACTION_SEND with an image". The <package> form is for a known integration. The <provider> form is for content providers you query.1

The implicit exceptions#

Some packages are always visible:

  • the app's own package,
  • the platform and a set of core system packages,
  • the app that installed the app (relevant for store apps),
  • packages the app has already interacted with — for example, one it has started or bound to.

The last one is subtle: visibility can be earned by interaction. An app that starts another app's activity can then see it. This is deliberate; it avoids breaking flows that already work.

What broke#

The migration pain came from three places:

  1. Deep links. Apps that checked whether a deep link would resolve by calling resolveActivity started getting null, so they opened a browser even when the app was installed. The fix is to declare the intent in <queries>.
  2. Share sheets. A custom share sheet that enumerated ACTION_SEND targets saw an empty list. The fix is an intent-shaped query.
  3. Analytics and anti-fraud. Code that fingerprinted the device by listing installed apps silently returned a short list, which changed behaviour in ways nobody noticed for months.

The third is the interesting one: it was the purpose of the change. Installed-app enumeration is a tracking signal, and the platform decided it should require a declaration.

QUERY_ALL_PACKAGES#

There is an escape hatch: QUERY_ALL_PACKAGES, a normal permission that restores the old behaviour. But Play treats it as restricted — an app that declares it must justify it, and approval is limited to cases such as security, antivirus and file management.

The existence of the permission is a recognition that some apps genuinely need it. The restriction is a recognition that most do not.

Visibility and package installers#

A related change arrived in Android 13: restricted settings for sideloaded apps. An app installed from outside a store may not be able to enable accessibility services or notification listeners without an extra confirmation.

The connection to visibility is conceptual rather than mechanical: both changes move the platform from "the app decides what it can reach" to "the user and the platform decide". Visibility controls what an app can see; restricted settings control what a sideloaded app can do.2

How to migrate#

The recipe is short:

  1. Find the calls: getInstalledPackages, getPackageInfo, queryIntentActivities, resolveActivity, and any library that wraps them.
  2. Classify each one: does it enumerate, or does it invoke?
  3. Declare the minimum <queries> for the enumerations.
  4. Test on API 30+ with no other apps installed — the emulator is good for this.
  5. Check libraries, because a transitive dependency can be the one calling getInstalledPackages.

Step 5 is where most time is lost. The app's own code is usually easy; the SDK inside it is not.

Debugging an empty list#

The symptom is usually a list that is empty or much shorter than expected. The diagnostic order that works:

  1. Check the manifest merge. <queries> from a library is merged into the app's manifest, and a merge conflict or a tools:node="remove" can silently drop an entry. app/build/intermediates/merged_manifests/ in a debug build shows the final result.
  2. Check whether the query is on the right API level. A <queries> element is ignored on API levels below 30, where the old behaviour applies. Code that assumes both behave the same will pass on an old emulator and fail on a new one.
  3. Check the intent shape. queryIntentActivities matches on action, category, data and MIME type. A query for ACTION_SEND without the MIME type will not match an app that only handles image/*.
  4. Check the interaction exception. An app that has already been started may be visible even without a declaration, which makes a bug appear only on a fresh install.

That last case is the most confusing, because the bug disappears after you do anything. It is worth testing with a freshly reset emulator rather than a device you have been using.

Why the design is a query language#

The interesting thing about <queries> is that it is declarative. The app does not ask "may I see all packages"; it asks "I am interested in apps that handle this intent". The platform can then answer the narrow question without exposing the full list.

That is the same shape as the permissions model: declare what you need, not what you might want. It is also why the migration is mostly mechanical once you understand it — the declaration is a description of intent, not a capability.

How it evolved#

Privacy changes8 releases
APIVersionWhat changed
184.3 Jelly Bean
  • App Ops (hidden) introduced for permission control
236.0 Marshmallow
  • requestPermissions() and the runtime permission flow
289 Pie
  • Foreground service permission
2910
  • Non-resettable identifiers restricted
3011
  • One-time permissions
3112
  • Bluetooth scan/connect permissions
3313 Tiramisu
  • POST_NOTIFICATIONS runtime permission
3717 Cinnamon Bun
  • ACCESS_LOCAL_NETWORK runtime permission for local network access
2008–2019Full enumerationAny app can list every installed package.API 1
2020 →Visibility filtering<queries> becomes mandatory for discovery.API 30
2022 →Restricted settingsSideloaded apps face extra friction for sensitive settings.API 33
2024 →Tighter defaultsMore APIs require explicit declarations rather than broad grants.

Under the hood#

Curiosity corner#

  • Visibility is per user. A work profile has a different package set, and the filter applies within the profile.
  • The launcher is exempt in practice. It needs to see every app, and it holds the permissions that make that possible.
  • A <queries> element with no matches is harmless. It costs nothing at runtime; it is a declaration, not a subscription.

Try it#

See what an app can see

adb shell dumpsys package queries adb shell pm list packages -3 adb shell cmd package query-activities --brief -a android.intent.action.SEND

Example output
Packages:
Package [com.example.app] (visible: self)
Package [com.android.settings] (visible: system)
Package [com.example.scanner] (visible: declared)

dumpsys package queries shows the declared queries and the resulting visibility sets. Comparing it with pm list packages -3 shows how much the app cannot see.

Check yourself#

Check yourself

1 / 12

An APK is fundamentally which kind of file?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsPackage visibility filtering on Androiddeveloper.android.comaccessed 2026-10-27↩
  2. docsBehavior changes: Apps targeting Android 13 or higherdeveloper.android.comaccessed 2026-10-27↩
  3. docsBehavior changes: Apps targeting Android 11developer.android.comaccessed 2026-10-27↩
  4. docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩
  5. docsConnectivity overviewsource.android.comaccessed 2026-10-27↩