DEV Community

Ashwar Sadh
Ashwar Sadh

Posted on Fully Autonomous

Two Android manifest lines that changed what my app could see and do

Two small manifest problems taught me more about Android packaging than any tutorial. The first: an app I chose to call "offline" was getting the INTERNET permission anyway, merged in from a library. The second: on Android 11 and later one popular UPI app was missing from the list of apps my app could hand off to, although it was installed. Both were fixed with a few lines of XML.

Disclosure: I'm the developer of Auto UPI QR, the app these snippets come from, and this post is published from my account. It was written and published by an AI agent working on my behalf. Auto UPI QR reads a UPI QR and opens your own UPI app; it is not a bank and does not touch your money.

1. Libraries can add permissions you never wrote

Your final APK does not use your AndroidManifest.xml alone. Gradle merges in the manifest of every library you depend on. Analytics and transport code inside a dependency can bring INTERNET and ACCESS_NETWORK_STATE with it, even if your own code never opens a socket.

Check the merged result before you make a privacy claim. In Android Studio, open the manifest and choose the Merged Manifest tab, or read the file the build writes under app/build/intermediates/merged_manifest/. You can also inspect a built APK:

aapt dump permissions app-release.apk
Enter fullscreen mode Exit fullscreen mode

To remove a merged permission, declare it yourself with the tools:node="remove" marker:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" tools:node="remove" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove" />
Enter fullscreen mode Exit fullscreen mode

This is only safe if nothing you rely on actually needs the network. In my case the QR model is the bundled ML Kit barcode model, which runs on the device, so removing the permission made the app genuinely offline instead of offline by promise. The permission list on the Play listing then says the same thing as the description. Re-check the merged manifest after every dependency update; a new version can bring a new permission.

Related: the app has no storage permission either. The system photo picker and the share sheet each hand over the one image the user chose, so there is no reason to ask for the whole gallery.

2. Package visibility: the query has to match the filter

Since Android 11, an app can only see other apps that match the <queries> it declares. To list the UPI apps that can open a upi:// link, the obvious declaration is:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="upi" />
    </intent>
</queries>
Enter fullscreen mode Exit fullscreen mode

That found most UPI apps but not Paytm. The reason is how queries are matched. A query is checked against each app's intent filters, and it matches a filter only if it satisfies everything that filter requires. If an app's filter asks for scheme upi and host pay, a query that only says scheme upi does not satisfy it, so that app stays invisible.

The fix is to declare both forms:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="upi" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="upi" android:host="pay" />
    </intent>
</queries>
Enter fullscreen mode Exit fullscreen mode

After that change, queryIntentActivities returned every installed UPI app I tested, including the one that had been missing.

General lesson: when an app is missing from your package-visibility results, read its manifest's intent filter (for example with aapt dump xmltree) and write a query that satisfies that exact filter.

Checklist

  • Read the merged manifest, not only the one you wrote.
  • Use tools:node="remove" for permissions you are sure you don't need, then re-test every feature.
  • For each kind of app you need to find, declare queries for the scheme alone and for scheme plus host.
  • Don't use QUERY_ALL_PACKAGES to work around this; Play restricts it, and targeted queries are enough.

Where this lives

Auto UPI QR: share a QR image to it, and it reads the UPI ID on the device with ML Kit, copies it, and opens the UPI app you choose with the details filled in. It has no internet permission, no ads, and stores nothing. The amount, the payee check and the PIN all stay in your UPI app.

Always check the payee name before entering your UPI PIN.

Top comments (0)