Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Inspecting Installed Components and Intent Resolution with PackageManager and adb

Hello! Last lesson, you separated two questions that are easy to conflate: Can Android launch an Activity for this Intent? and Which handlers can my app discover? This lesson turns those questions into observations. You will inspect component declarations, query matching Activities from Kotlin, and use adb to compare what is installed with what actually launches.

The aim is a repeatable diagnostic method, not a preflight check before every startActivity() call. Package inspection tells you what is visible at a particular moment; launching remains a separate operation.


Three views of the same device

Suppose an implicit ACTION_SEND Intent with MIME type text/plain opens a share target. There are at least three useful views of that event:

  1. Package inventory: Which packages and manifest-declared components are installed?
  2. Intent query: Which Activities does a particular caller discover as matches?
  3. Launch: What happens when the system processes the Intent now?

These views need not produce identical-looking results. A package can be installed without declaring a matching Activity. An Activity can be declared but disabled, inaccessible to another app, or missing the required filter. On Android 11 and later, your app’s package-visibility rules can also limit its discovery results. Finally, resolution can involve user defaults or a resolver UI; a list of candidates is not a record of what the user selected.

Android Developers’ Intents and intent filters describes PackageManager queries as inspection rather than activation. Read its Intent matching subsection for the distinction between methods that list candidates and methods that resolve one.

Intents and intent filters | App architecture

Read Android Developers’ explanation of what PackageManager can discover without starting a component.

In Intent matching, begin at the paragraph starting “Your application can use intent matching in a manner similar to what the Home app does.” Read the query distinction through the explanation that these methods do not activate components. Notice that Activity, service, and broadcast-receiver queries are different APIs; this lesson’s resolution examples concern Activities.

A short demonstration makes the first view concrete. Philipp Lackner’s Intents & Intent Filters - Android Basics 2023 shows package listing on an emulator. Watch only the package-listing segment; the Kotlin and adb work below will go further than the video.

Intents & Intent Filters - Android Basics 2023

Watch the package-listing demonstration in Philipp Lackner’s video to see the difference between installed package identifiers and visible launcher icons.

Watch package listing. Focus on adb shell and pm list packages, and on why an installed package need not appear as an app icon.


Inspect resolution from Kotlin

Start with the same request throughout the lesson. Keeping the action and MIME type fixed makes differences in output easier to interpret:

val request = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
}

queryIntentActivities() asks for matching Activities; resolveActivity() asks for a resolution result. For a comparison with an implicit Activity launch, use MATCH_DEFAULT_ONLY: it limits the query to filters eligible for the CATEGORY_DEFAULT behavior discussed last lesson.

The following diagnostic code uses the Android 13 (API 33) flag overloads. If your app runs on older versions, use the corresponding Int-flags overload in a version-appropriate branch.

fun inspectTextShareHandlers(context: Context) {
    val request = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
    }
    val pm = context.packageManager
    val flags = PackageManager.ResolveInfoFlags.of(
        PackageManager.MATCH_DEFAULT_ONLY.toLong()
    )

    val candidates = pm.queryIntentActivities(request, flags)
    val resolved = pm.resolveActivity(request, flags)

    Log.d("IntentProbe", "Visible matching Activities: ${candidates.size}")
    candidates.forEach { result ->
        val activity = result.activityInfo
        Log.d(
            "IntentProbe",
            "Candidate: ${activity.packageName}/${activity.name}"
        )
    }

    val selected = resolved?.activityInfo
    Log.d(
        "IntentProbe",
        "Resolution result: " +
            if (selected == null) "none visible"
            else "${selected.packageName}/${selected.name}"
    )
}

Read the two outputs differently. candidates is a list of matches visible to this app. resolved is one resolution result, which can be a system resolver Activity when a choice remains; it is not necessarily one of the share targets the user will ultimately pick. Neither call launches a target, and neither result guarantees that a later launch will succeed.

To inspect declarations rather than matches, use getPackageInfo(). For example, this reports the manifest-declared components in your own installed package:

fun inspectOwnDeclaredComponents(context: Context) {
    val pm = context.packageManager
    val componentFlags =
        PackageManager.GET_ACTIVITIES or
        PackageManager.GET_SERVICES or
        PackageManager.GET_RECEIVERS or
        PackageManager.GET_PROVIDERS

    val info = pm.getPackageInfo(
        context.packageName,
        PackageManager.PackageInfoFlags.of(componentFlags.toLong())
    )

    info.activities.orEmpty().forEach {
        Log.d("ComponentProbe", "Activity: ${it.name}; exported=${it.exported}")
    }
    info.services.orEmpty().forEach {
        Log.d("ComponentProbe", "Service: ${it.name}; exported=${it.exported}")
    }
    info.receivers.orEmpty().forEach {
        Log.d("ComponentProbe", "Receiver: ${it.name}; exported=${it.exported}")
    }
    info.providers.orEmpty().forEach {
        Log.d("ComponentProbe", "Provider: ${it.name}; exported=${it.exported}")
    }
}

This inventory does not mean that every listed component accepts the text-share Intent. getPackageInfo() describes installed declarations; the Intent query tests a specific request against eligible filters. It also does not enumerate context-registered receivers, which are not manifest declarations.

You can inspect another package by supplying its package name, but treat a failure or a NameNotFoundException carefully: under package-visibility limits, “my app cannot inspect this package” does not necessarily mean “the package is not installed.” If your feature genuinely needs cross-app discovery, use the narrowly scoped <queries> declaration from the previous lesson. Keep installed-app lists out of production logs unless they are needed for a justified diagnostic purpose.


Inspect the installed device with adb

adb provides a complementary, device-side view. Android Developers’ Android Debug Bridge (adb) reference documents pm package queries and the am commands used to send a test Intent. Read the specified rows and argument list rather than the whole command reference.

Android Debug Bridge (adb)  |  Android Studio  |  Android Developers

Read the pm command table to identify what package listing establishes—and what it does not establish about individual Intent handlers.

In Call package manager (pm), find Table 2: Available package manager commands. Read the list packages row, including its -3, -e, and -d options: package-list options. Also locate the path package, enable package_or_component, and disable package_or_component rows. You only need to recognize the last two here; changing component state is not required for this lesson.

Android Debug Bridge (adb)  |  Android Studio  |  Android Developers

Read how am start launches an Activity and how command-line arguments specify the same action, category, MIME type, and extras that you set in Kotlin.

In Call activity manager, find Table 1: Available activity manager commands and its start [options] intent row; the launch options cover start and -W. Then, in Specification for intent arguments, read the argument list from its introduction through the string-extra option. Pay particular attention to -a, -d, -t, -c, -n, and --es.

With an emulator or test device connected, use the following as a diagnostic sequence. Replace com.example.intentprobe with the package name of an app you installed for testing.

adb devices
adb shell pm list packages -3
adb shell pm list packages com.example.intentprobe
adb shell pm path com.example.intentprobe
adb shell dumpsys package com.example.intentprobe

The filtered list packages result establishes whether the package is present for the queried device/user. pm path identifies its installed APK path; it says nothing about whether a particular Intent matches. In the potentially long dumpsys package output, search for your package’s Activities and Activity Resolver Table entries. Look for the Activity class, exported state, and the action, category, and MIME type in its filter. Output organization varies by Android version, so search for the class name and action if those section labels differ.

You can also ask the device’s package command to display Activity query and resolution results. First check the commands available on your test device:

adb shell cmd package help

On devices that support query-activities and resolve-activity, use the same Intent specification for both:

adb shell cmd package query-activities --brief \
  -a android.intent.action.SEND \
  -c android.intent.category.DEFAULT \
  -t text/plain

adb shell cmd package resolve-activity --brief \
  -a android.intent.action.SEND \
  -c android.intent.category.DEFAULT \
  -t text/plain

If a device rejects an option, consult its cmd package help output rather than assuming every Android release exposes identical shell syntax. These shell queries are especially useful for spotting a filter you overlooked. Do not expect their output to be identical to your app’s PackageManager output: the shell command and your app are different callers, and your Kotlin query explicitly uses MATCH_DEFAULT_ONLY.

Finally, test launch separately, using harmless sample text:

adb shell am start -W \
  -a android.intent.action.SEND \
  -c android.intent.category.DEFAULT \
  -t text/plain \
  --es android.intent.extra.TEXT "Diagnostic sample"

-W waits for the launch attempt to complete. The resulting UI might be a target, a resolver, or an error if nothing can handle the request. A successful am start shows that this device-side launch attempt worked; it does not prove that the user completed a share. Conversely, an unsuccessful shell test deserves investigation of the exact Intent arguments, device state, and component declarations before you conclude that no handler exists.


Reconcile a surprising result

When the Kotlin candidate list, shell output, and observed UI disagree, compare them in this order:

  1. Confirm identical requests. Check action, category, URI, and MIME type. An extra such as EXTRA_TEXT does not change filter matching. Ensure the Kotlin query uses MATCH_DEFAULT_ONLY when comparing it with an Activity launch.
  2. Inspect the installed declaration. Verify that the package and Activity exist, that the component is enabled and accessible to the caller, and that its filter accepts the request. A package name alone is not evidence of a matching Activity.
  3. Account for the caller. Package-visibility limits apply to discovery from your app. adb shell observations are diagnostic evidence, not a substitute for observing your app’s own query results.
  4. Observe launch independently. A query is a snapshot, not a reservation. Defaults, resolver UI, or a change in installed or enabled components can affect what happens next.

This is why queryIntentActivities() belongs in features that actually need to display or examine available handlers—not as an obligatory guard before every launch. If the feature only needs to open a handler, retain last lesson’s simpler approach: attempt startActivity() and handle ActivityNotFoundException.


Key takeaways

Use getPackageInfo() to inspect manifest-declared components, queryIntentActivities() to inspect visible matches for a particular Intent, and resolveActivity() to inspect a resolution result. Use adb shell pm and dumpsys package to investigate installation and declarations, cmd package where available to compare device-side resolution, and am start to test an actual launch. Always record the caller and exact Intent when comparing results.

Next, you will move from discovering components to configuring your own Application initialization, with particular attention to Context lifetime and work performed on the main thread.

Can't find a good explanation? Sign up and we'll make it for you

Sign up