Exported components after Android 12: inspect without Android Studio

An activity, service, or receiver with an intent-filter and no explicit android:exported fails install on Android 12+. Our validator will not fail that yet. Here is the desk check.

The rule, without the folklore

If a component has an <intent-filter>, you must set android:exported to true or false explicitly when targetSdk is 31+. true means other apps (and the system) may invoke it. Deep-link activities are usually true. A receiver that should only hear your broadcasts is false.

Libraries still ship filters without the attribute. The merge looks fine in a quick glance and dies on a Pixel running 14. The install error mentions the component name. People then “fix” the wrong activity in main and leave the library merge broken.

What DroidXP checks today

The Manifest Validator uses the browser DOMParser on text XML. It checks well-formedness, package shape, <application>, uses-sdk, and MAIN/LAUNCHER. It does not walk every component for missing exported. A green report is not an install certificate.

The APK Analyzer does not decode binary AXML into a component tree. You cannot see exported flags there. Do not drop an APK on the validator either — paste decoded text.

Worked example — before / after

This fragment is valid XML and will not fail our validator. On API 31+ it can fail install:

<activity android:name=".LinkActivity">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="app.example.com" />
  </intent-filter>
</activity>

Fix:

<activity android:name=".LinkActivity" android:exported="true">
  <!-- same intent-filter -->
</activity>

Then re-validate for well-formedness, and test the URL in the Deep link tester (syntax only) plus a device tap. Exported true without a tight <data> filter is a different problem (other apps can trigger you). That is product/security, not this install rule.

Receivers: a BOOT_COMPLETED receiver that you only want your app to send should be exported="false" if it has no filter that the system must hit — but BOOT_COMPLETED is delivered by the system and typically needs the filter plus the right exported value for your targetSdk. Read the current docs for that action; do not copy a 2018 snippet. Services with filters are the same class of bug as activities.

How to inspect without Studio

  1. apktool decode, or copy the merged text manifest from CI.
  2. Search for intent-filter. For each parent activity/service/receiver, confirm android:exported=.
  3. Paste the file into the Manifest Validator so you are not also fighting broken tags.
  4. If you only have an APK: aapt dump xmltree your.apk AndroidManifest.xml and read exported in the tree. Missing attribute shows up as no android:exported field on that node.

Lint in AGP already has this check (ExportedReceiver / related). If CI does not run lint on the app module, you will keep rediscovering it on a device. ./gradlew :app:lintDebug is cheaper than a Play pre-launch report.

Deep links make this worse

A filter that should open the app must be exported. A filter you added for a debug dump activity must not be. Flavor-specific manifests are where the wrong value survives: debug exported true for a .DumpActivity that ships in a misconfigured release merge. See ADB and intents without Studio.

App Links verification does not run if the activity is not exported. You can host a perfect assetlinks.json and still see “doesn’t handle” because install failed or the activity is not visible to other apps.

Tools vs install

Green validator + red device is the expected pair until we add the exported walk. Treat the validator as XML hygiene. Treat lint and a 12+ emulator as the install check. The APK analysis guide sits in front of this when you only have a ZIP.

When to leave the browser

Studio Merged Manifest, ./gradlew :app:lint, and a physical Android 12+ install. We will not pretend a ZIP listing replaces those. Job table: browser vs apktool.