Android Intent Filter With Code: 2026 Guide (Examples, Demo App + Fixes)

An intent filter is a short block of XML in AndroidManifest.xml that tells Android: "send this kind of request to my app." It is how your app joins the share sheet, opens a link from Chrome, or appears on the home screen. Every filter needs an <action>. Almost every filter also needs android.intent.category.DEFAULT and an explicit android:exported value — miss either one and your app either fails to build or silently never appears.
Here is why this guide exists. We read the top-ranking intent filter tutorials before writing it. Most of them show manifest XML that will not build on Android 12 or newer, because they never mention android:exported. None of them explain the strict matching rule Android 13 added. None show you how to test a filter with adb. This guide gives you code that compiles against Android 16 (API 36), in both Kotlin and Java, plus the fix list for the question people actually ask second: "my filter is right there in the manifest, so why is nothing happening?"
In a hurry? Jump to: copy-paste examples · full demo app (5 steps) · attribute reference tables · rules that changed (2021–2026) · why your filter is not working · test it with adb
What Is an Intent Filter, in Plain English?
Think of your phone as a busy office. An intent is a note that says "someone please open this PDF." An intent filter is the sign on your office door that says "I open PDFs."
Android reads every door sign on the phone, finds the ones that match, and either opens your app straight away or shows a chooser so the user picks.
There are two kinds of intents:
- Explicit intent — you name the exact class to open. Used inside your own app. No filter needed.
- Implicit intent — you describe what you want done ("share this text") and let Android find an app. Intent filters exist only for this kind.
If you only move between your own screens, you do not need intent filters. You need them when you want to talk to other apps — or let other apps talk to you.
The 3 Tests Every Intent Must Pass
When an implicit intent goes out, Android checks it against your filter three times. Your app wins only if it passes all three.
| Test | What Android checks | The rule that trips people up |
|---|---|---|
| Action | Does the intent's action match one <action> in your filter? | A filter with zero actions matches nothing. Ever. |
| Category | Does your filter contain every category the intent carries? | startActivity() silently adds CATEGORY_DEFAULT. If you did not declare it, you lose. Your filter may list extra categories; that is fine. |
| Data | Does the URI (scheme, host, port, path) and MIME type match? | URI and MIME type are judged together. An intent with no data passes only if your filter declares no <data> at all. |
The category test is where almost everyone loses an afternoon. Remember one line:
If you want other apps to reach you, declare android.intent.category.DEFAULT. The only exception is the launcher entry, which uses MAIN + LAUNCHER instead.
Copy-Paste Intent Filter Examples That Work in 2026
Every block below builds against API 36. Paste them straight into AndroidManifest.xml.
1. The launcher icon (every app has this one)
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This is the only activity that should be exported="true" by default. It is the front door.
2. Show up in the share sheet (receive text and images)
<activity
android:name=".ShareActivity"
android:label="Save to My App"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*" />
</intent-filter>
<!-- Multiple items is a different action, so it gets its own filter. -->
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
The android:label is worth setting. The share sheet sorts entries by the filter's label, and falls back to your app name when there is none — so the label quietly controls where you appear in the list.
3. Read what you were handed — Kotlin and Java
Kotlin:
class ShareActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share)
// Never assume the action is what you expect.
when (intent?.action) {
Intent.ACTION_SEND -> {
if (intent.type == "text/plain") {
val shared = intent.getStringExtra(Intent.EXTRA_TEXT)
// Treat this as untrusted input. Validate before you use it.
showText(shared.orEmpty())
}
}
Intent.ACTION_VIEW -> showLink(intent.data) // deep links land here
else -> finish()
}
}
}
Java, for the same activity:
public class ShareActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share);
Intent intent = getIntent();
String action = intent.getAction(); // may be null
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && "text/plain".equals(type)) {
String shared = intent.getStringExtra(Intent.EXTRA_TEXT);
showText(shared == null ? "" : shared);
} else if (Intent.ACTION_VIEW.equals(action)) {
showLink(intent.getData());
} else {
finish();
}
}
}
Note the equals() order in Java. Write Intent.ACTION_SEND.equals(action), not action.equals(...) — the action can be null and will crash you.
4. Open your website links inside your app (App Links)
<activity
android:name=".DeepLinkActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<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="www.androidhire.com"
android:pathPrefix="/deals" />
</intent-filter>
</activity>
Two categories here, not one. BROWSABLE is what lets a browser hand the link over.
autoVerify="true" only works if you also host a Digital Asset Links file at https://yourdomain.com/.well-known/assetlinks.json, served as application/json over HTTPS, containing your app's signing certificate fingerprint. Play Console gives you the exact snippet. Miss that file and Android quietly demotes your link to "ask the user every time."
A detail almost no tutorial mentions: on Android 15 and higher the system re-verifies your domains in the background, and a fix to assetlinks.json can take up to seven days to reach every device. Do not panic-refactor your manifest on day two.
5. A custom scheme (myapp://)
<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="myapp" android:host="profile" />
</intent-filter>
Custom schemes are easy but nobody owns them. Any other app can claim myapp:// too. Use HTTPS App Links for anything carrying a token, an account ID, or a payment.
6. Broadcast receivers and services
A manifest-declared receiver, listening for a system broadcast:
<receiver
android:name=".BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Register one at runtime instead, and Android 14+ forces you to say who can reach it:
ContextCompat.registerReceiver(
context,
myReceiver,
IntentFilter("com.example.ACTION_SYNC_DONE"),
ContextCompat.RECEIVER_NOT_EXPORTED // or RECEIVER_EXPORTED
)
For services, do not use implicit intents at all. Android has thrown on implicit service intents since 5.0. Always name the class, or name the package:
val intent = Intent("com.example.ACTION_UPLOAD").apply {
setPackage("com.example.otherapp") // makes it explicit enough
}
context.startService(intent)
7. A custom action for your own apps (protected)
<activity
android:name=".TodoActivity"
android:exported="true"
android:permission="com.example.permission.EDIT_TODO">
<intent-filter>
<action android:name="com.example.action.EDIT_TODO" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Use your package name as the prefix for custom actions. And add android:permission so only apps you trust can fire it — an exported component without a permission is open to every app on the device.
8. Sending the intent (the part most tutorials skip)
val sendIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Check this out")
}
// Do NOT use resolveActivity() as your guard on Android 11+.
// Package visibility can make it return null even when an app
// exists, so your share button just dies silently.
try {
startActivity(Intent.createChooser(sendIntent, "Share via"))
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "No app can handle this", Toast.LENGTH_SHORT).show()
}
Sharing a file or image instead of text? You must grant read access on the URI, or the receiving app gets a SecurityException:
val shareImage = Intent(Intent.ACTION_SEND).apply {
type = "image/jpeg"
putExtra(Intent.EXTRA_STREAM, contentUri) // from a FileProvider
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(shareImage, "Share image"))
And if you really do need to check for a handling app first, declare what you are allowed to see (Android 11+):
<!-- top level of AndroidManifest.xml, next to <application> -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
</queries>
Without that <queries> block, resolveActivity() and queryIntentActivities() return nothing on Android 11 and up — and the feature looks broken for no visible reason. The system chooser is exempt, which is one more reason to prefer createChooser().
Build a Working Demo App in 5 Steps
This is a complete two-screen app. MainActivity shares text out. ReceiverActivity catches text shared from any app. Build it once and the whole idea clicks.
Step 1 — the layout (res/layout/activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<EditText
android:id="@+id/inputText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something to share" />
<Button
android:id="@+id/shareButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Share" />
</LinearLayout>
Step 2 — send the intent (MainActivity.kt):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val input = findViewById<EditText>(R.id.inputText)
findViewById<Button>(R.id.shareButton).setOnClickListener {
val send = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, input.text.toString())
}
try {
startActivity(Intent.createChooser(send, "Share with"))
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "Nothing can handle that", Toast.LENGTH_SHORT).show()
}
}
}
}
Step 3 — receive the intent (ReceiverActivity.kt):
class ReceiverActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_receiver)
val label = findViewById<TextView>(R.id.resultText)
if (intent?.action == Intent.ACTION_SEND && intent.type == "text/plain") {
label.text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: "Empty"
} else {
label.text = "Opened directly, no shared text"
}
}
}
Step 4 — wire up the manifest. This is the step every broken tutorial gets wrong:
<application ... >
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ReceiverActivity"
android:label="Send to Demo App"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
Step 5 — run it. Install the app, then open Chrome or Google Keep, select some text, and tap Share. "Send to Demo App" now appears in the share sheet. Tap it and your text shows up on the second screen.
Want proof without leaving your desk? Fire the same intent from the terminal with the adb commands below.
Intent Filter Reference Tables
<intent-filter> attributes
| Attribute | What it does |
|---|---|
android:label | Name shown in the chooser or share sheet. Also affects share sheet ordering. Falls back to the app name. |
android:icon | Icon shown in the chooser. Falls back to the activity or app icon. |
android:priority | Ranks this filter against others. On Android 16, ordered-broadcast priority only applies within your own process. |
android:order | Orders filters within the same app when several match. Does not affect other apps. |
android:autoVerify | Asks Android to verify you own the domain via assetlinks.json. App Links only. |
<data> attributes (URI matching)
| Attribute | Matches | Example |
|---|---|---|
android:scheme | The part before ://. Required before host works. | https, myapp, content |
android:host | Domain. A leading * wildcard is allowed. | www.androidhire.com, *.example.com |
android:port | Port number. Only used if scheme and host are set. | 8080 |
android:path | Exact full path match. | /deals/today |
android:pathPrefix | Path starts with this. | /deals |
android:pathPattern | Simple glob. .* and * only. | /user/.*/profile |
android:pathSuffix | Path ends with this. | .pdf |
android:mimeType | Content type. Wildcards allowed. | text/plain, image/* |
The actions and categories you will actually use
| Constant | Manifest string | Used for |
|---|---|---|
ACTION_MAIN | android.intent.action.MAIN | App entry point. Pair with LAUNCHER. |
ACTION_VIEW | android.intent.action.VIEW | Open a link, map, file, or contact. The most common action of all. |
ACTION_SEND | android.intent.action.SEND | Share one item. Needs a mimeType. |
ACTION_SEND_MULTIPLE | android.intent.action.SEND_MULTIPLE | Share several items at once. |
ACTION_SENDTO | android.intent.action.SENDTO | Email or SMS to a specific address, via a mailto: or smsto: scheme. |
ACTION_DIAL | android.intent.action.DIAL | Open the dialer with a number. Needs no permission. |
ACTION_CALL | android.intent.action.CALL | Place a call directly. Needs CALL_PHONE permission. |
ACTION_GET_CONTENT | android.intent.action.GET_CONTENT | Let the user pick a file or image. |
ACTION_EDIT / ACTION_PICK | ...action.EDIT / ...action.PICK | Edit an item, or pick one from a list. |
CATEGORY_DEFAULT | android.intent.category.DEFAULT | Required to accept implicit intents. |
CATEGORY_LAUNCHER | android.intent.category.LAUNCHER | Puts an icon on the home screen. |
CATEGORY_BROWSABLE | android.intent.category.BROWSABLE | Required so a browser can hand a link to you. |
The Rules That Changed: Android 11 Through 16
If you copied an intent filter from an older blog post, this table explains why it broke.
| Version | What changed | What it means for you |
|---|---|---|
| Android 11 (API 30) | Package visibility | You must declare <queries> to see other apps. resolveActivity() may return null. |
| Android 12 (API 31) | android:exported is required | Any component with an intent filter must set it explicitly, or the build fails and the app cannot install. |
| Android 12 (API 31) | PendingIntent mutability | You must pass FLAG_IMMUTABLE or FLAG_MUTABLE. |
| Android 13 (API 33) | Strict filter matching | Intents from other apps reach an exported component only if they truly match its filter. Applies no matter what the sending app targets. |
| Android 14 (API 34) | Receivers and pending intents tightened | Runtime-registered receivers must pass RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED; implicit intents to non-exported components throw. |
| Android 15 (API 35) | App Links re-verified in background | Verification changes can take up to 7 days to reach all devices. |
| Android 16 (API 36) | Intent redirect defense + broadcast priority | Stronger built-in protection against intent redirection attacks. android:priority for ordered broadcasts is honored only within the same process. |
Why this matters right now: from August 31, 2026, new apps and updates on Google Play must target Android 16 (API 36), with extensions available until November 1, 2026. Every rule above is already live for anyone shipping this year.
Why Your Intent Filter Is Not Working (11 Real Causes)
This is the section people actually search for. Find your symptom, apply the fix.
| Symptom | Most likely cause | Fix |
|---|---|---|
| Build fails on install | Missing android:exported | Add android:exported="true" or "false" to every component that has a filter. |
| App never appears in the share sheet | No DEFAULT category | Add <category android:name="android.intent.category.DEFAULT" />. |
| Links open in Chrome, not your app | No BROWSABLE category, or failed verification | Add BROWSABLE, then check assetlinks.json is live and correct. |
| App Link verification says "none" | Wrong SHA-256 fingerprint, a redirect, or wrong content-type | Use the Play Console app-signing fingerprint. No redirects. Serve as application/json. |
| Works on your phone, not on users' phones | Android Studio installs auto-approve links | Test a Play or sideloaded build and re-check verification state. |
| Share button does nothing | resolveActivity() returning null (Android 11+) | Use try/catch around startActivity(), or declare <queries>. |
ActivityNotFoundException after a target SDK bump | Android 13 strict matching | Make the intent's action and categories match the filter exactly. |
SecurityException when sharing an image or file | No URI permission granted | Add FLAG_GRANT_READ_URI_PERMISSION and use a FileProvider URI. |
| Filter matched but the data is empty | Reading the wrong extra | ACTION_SEND uses EXTRA_TEXT / EXTRA_STREAM; ACTION_VIEW uses intent.data. |
| Two filters merged and now match too much | Elements stacked in one <intent-filter> | Android combines everything inside one filter. Split unrelated pairings into separate filters. |
| Receiver never fires on Android 14+ | Missing export flag at registration | Pass RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED to registerReceiver(). |
The "merged filter" one is subtle and worth repeating. A filter holding two actions and two MIME types does not mean two rules — it means every combination is accepted. Want specific pairings only? Write separate <intent-filter> blocks.
One more rule that saves you from bug reports: do not copy intent filters that are not yours. Pasting another app's manifest entries makes your app volunteer for intents it cannot handle, and users see your icon in places it does not belong.
Test Your Intent Filter in 60 Seconds With adb
Do not guess. Fire an intent at your own app from the terminal.
# 1. Send a plain-text share intent
adb shell am start -a android.intent.action.SEND \
-t text/plain --es android.intent.extra.TEXT "hello"
# 2. Open a deep link
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://www.androidhire.com/deals"
# 3. Check App Link verification status (Android 12+)
adb shell pm get-app-links com.your.package
# 4. See every filter the system has registered for your app
adb shell dumpsys package com.your.package | grep -A 20 "Activity Resolver"
Command 3 is the one that saves hours. If it prints none or 1024 next to your domain, your assetlinks.json is the problem — not your manifest. Force a re-check with:
adb shell pm verify-app-links --re-verify com.your.package
Command 4 is the reality check: if your filter is not in that dump, Android never registered it, and no amount of code changes will help.
Treat Every Incoming Intent as Untrusted
An intent filter is a doorbell, not a lock. Anything can ring it, including a malicious app.
- Set
android:exported="false"on anything that does not genuinely need outside access. - Add
android:permissionto exported components meant only for your own apps. - Validate extras before use. Never feed a received URI straight into a WebView, a file read, or SQL.
- Never forward an intent you received to
startActivity()unchecked — that is intent redirection, the exact attack Android 16 hardened against. - Use explicit intents for services. Implicit service intents have been blocked since Android 5.0.
- Use
FLAG_IMMUTABLEon PendingIntents unless you have a specific reason not to.
Quick Cheat Sheet
- Launcher icon?
MAIN+LAUNCHER+exported="true" - Share sheet?
SEND+DEFAULT+<data android:mimeType> - Web link?
VIEW+DEFAULT+BROWSABLE+autoVerify="true"+assetlinks.json - Broadcast receiver at runtime?
RECEIVER_EXPORTEDorRECEIVER_NOT_EXPORTED - Service? Explicit intent, or
setPackage(). Never implicit. - Internal only?
exported="false"and an explicit intent - Broke after a target SDK bump? Re-read the version table
How We Verified This Guide
We rebuilt every snippet here against API 36 (Android 16) in Android Studio and confirmed each one compiles and installs. That matters because the most common complaint about older intent-filter tutorials is that their XML no longer builds under the Android 12 exported rule — and when we read the current top-ranking guides for this topic before writing, most of them still omit android:exported entirely, and none of them cover App Links verification, the Android 13 matching rule, or how to test a filter with adb.
Each behavior claim was cross-checked against Google's official Intents and intent filters documentation, the <intent-filter> manifest reference, the Android 16 behavior changes notes, the App Links verification guide, and Google Play's published target API level requirements. Where a rule depends on who sends the intent versus who receives it — a distinction most tutorials blur — we followed Google's wording exactly.
Bottom line: an intent filter is three small tests — action, category, data — wrapped in XML that Android has tightened six times since 2020. Declare your action, add DEFAULT (plus BROWSABLE for links), set android:exported on purpose, then prove it works with adb shell am start before you ship. Next step: build the 5-step demo app above, run the adb command, and watch your app appear in the share sheet. Then keep going with our guide to sending email in Android using an intent, or brush up on Android runtime permissions and the fragment lifecycle while you are in the manifest.
Frequently Asked Questions
What is an intent filter in Android?
An intent filter is an XML block in AndroidManifest.xml that tells Android which implicit intents your activity, service, or broadcast receiver can handle. It is how your app appears in the share sheet, opens links from a browser, or shows up on the home screen. Each filter can contain <action>, <category>, and <data> elements, and an incoming intent must pass all three tests to reach your component.
What is the difference between an intent and an intent filter?
An intent is the request ('open this link', 'share this text'). An intent filter is your app's advertisement saying it can handle that kind of request. Intents are created in Kotlin or Java at runtime; intent filters are declared in AndroidManifest.xml at build time. Explicit intents name a class directly and ignore filters completely; only implicit intents are matched against filters.
Why is my intent filter not working?
The most common causes are: a missing android.intent.category.DEFAULT category, a missing BROWSABLE category on deep links, no android:exported value (required since Android 12), a failed assetlinks.json verification for App Links, or an intent whose action and categories do not exactly match the filter. Android 13 and higher only deliver an external intent to an exported component when it genuinely matches the declared filter. Run 'adb shell pm get-app-links your.package' to check link verification, and 'adb shell dumpsys package your.package' to confirm the filter was registered at all.
Do I need android:exported on every intent filter?
Yes. Apps targeting Android 12 (API 31) or higher must set android:exported explicitly on every activity, service, and broadcast receiver that declares an intent filter. If you leave it out, the build fails and the app will not install. Use true only when other apps genuinely need access; use false for everything internal.
Can an intent filter have more than one action?
Yes, but be careful. Android treats everything inside a single <intent-filter> as combinable, so two actions and two MIME types means all four combinations are accepted. If you only want specific pairings, write separate <intent-filter> blocks instead of stacking elements into one.
What is autoVerify in an intent filter?
android:autoVerify="true" tells Android to check whether you really own the domain in your <data> element. Android fetches https://yourdomain.com/.well-known/assetlinks.json and compares your app's signing certificate fingerprint. If it matches, your links open directly in your app with no chooser dialog. On Android 15 and higher the system re-verifies periodically in the background, and changes can take up to seven days to reach all devices.
How do I test an intent filter without another app?
Use adb. Run 'adb shell am start -a android.intent.action.SEND -t text/plain --es android.intent.extra.TEXT "hello"' to fire a share intent, or 'adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourdomain.com/path"' for a deep link. Use 'adb shell pm get-app-links your.package' to see App Link verification status, and 'adb shell dumpsys package your.package' to confirm the system registered your filter.
Are intent filters a security feature?
No. An intent filter is a doorbell, not a lock. Any app can craft an intent that matches your filter, so always validate incoming data, set android:exported="false" on internal components, add android:permission to exported ones meant only for your own apps, never forward a received intent without checking it, and use FLAG_IMMUTABLE on PendingIntents. Android 16 adds default protection against intent redirection attacks, but your own validation is still required.
Can I use an intent filter for a service or broadcast receiver?
Broadcast receivers, yes. Services, effectively no. Implicit intents to services have been blocked since Android 5.0, so always name the class or call setPackage() on the intent. For manifest-declared receivers you still write a normal <intent-filter> with android:exported set. For receivers registered at runtime, Android 14 and higher requires you to pass RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED to registerReceiver().
What is the difference between a deep link, an App Link, and a custom scheme?
A custom scheme like myapp:// is the simplest, but nobody owns it, so any other app can claim the same scheme. A plain HTTP deep link works but usually shows a chooser dialog. An Android App Link is an HTTPS link with android:autoVerify="true" plus a matching assetlinks.json on your domain, so Android confirms you own the site and opens your app directly with no dialog. Use App Links for anything carrying a token, account ID, or payment.
Why does resolveActivity() return null on newer Android versions?
Because of package visibility, added in Android 11 (API 30). Your app can only see other packages you declare in a <queries> element in the manifest. Old sample code that guards a share button with 'if (intent.resolveActivity(packageManager) != null)' now fails silently on many devices. Either declare <queries>, or drop the check and wrap startActivity() in a try/catch for ActivityNotFoundException. The system chooser created by Intent.createChooser() is exempt from these restrictions.
Which Android version rules affect intent filters in 2026?
Android 11 added package visibility (resolveActivity can return null), Android 12 made android:exported mandatory, Android 13 added strict filter matching for external intents, Android 14 required RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED on runtime-registered receivers, Android 15 re-verifies App Links in the background, and Android 16 hardened intent redirection and limited android:priority ordering to a single process. New apps and updates on Google Play must target API 36 from August 31, 2026, with extensions available until November 1, 2026.


