intent.resolveActivity returns null in API 30

66.1k Views Asked by At

Looking at intent.resolveActivity != null but launching the intent throws an ActivityNotFound exception I wrote opening a browser or an application with Deep linking:

private fun openUrl(url: String) {
    val intent = Intent().apply {
        action = Intent.ACTION_VIEW
        data = Uri.parse(url)
//        setDataAndType(Uri.parse(url), "text/html")
//        component = ComponentName("com.android.browser", "com.android.browser.BrowserActivity")
//        flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    val activityInfo = intent.resolveActivityInfo(packageManager, intent.flags)
    if (activityInfo?.exported == true) {
        startActivity(intent)
    } else {
        Toast.makeText(
            this,
            "No application can handle the link",
            Toast.LENGTH_SHORT
        ).show()
    }
}

It doesn't work. No browser found in API 30 emulator, while a common solution works:

private fun openUrl(url: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
    try {
        startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        Toast.makeText(
            this,
            "No application can handle the link",
            Toast.LENGTH_SHORT
        ).show()
    }
}

The first method doesn't work, because intent.resolveActivityInfo or intent.resolveActivity returns null. But for PDF-viewer it works.

Should we dismiss intent.resolveActivity?

11

There are 11 best solutions below

8
Mike M. On BEST ANSWER

This appears to be due to the new restrictions on "package visibility" introduced in Android 11.

Basically, starting with API level 30, if you're targeting that version or higher, your app cannot see, or directly interact with, most external packages without explicitly requesting allowance, either through a blanket QUERY_ALL_PACKAGES permission, or by including an appropriate <queries> element in your manifest.

Indeed, your first snippet works as expected with that permission, or with an appropriate <queries> element in the manifest; for example:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
</queries>

The information currently available isn't terribly specific, but it does state:

The PackageManager methods that return results about other apps, such as queryIntentActivities(), are filtered based on the calling app's <queries> declaration

Though your example is using an Intent method – i.e., resolveActivityInfo() – that's actually calling PackageManager "query" methods internally. An exhaustive list of every method and functionality affected by this change might not be feasible, but it's probably safe to assume that if PackageManager is involved, you might do well to check its behavior with the new restrictions.

0
Dylan On

What Mike said in addition to the code below is what worked for me.

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>
5
CoolMind On

Thanks to Mike M. I added queries for Browser, Camera and Gallery. Place them inside AndroidManifest in any part of it (before or after <application> tag).

Looking at MediaStore.ACTION_IMAGE_CAPTURE and Intent.ACTION_GET_CONTENT I got both actions.

<queries>
    <!-- Browser -->
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="http" />
    </intent>

    <!-- Camera -->
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>

    <!-- Gallery -->
    <intent>
        <action android:name="android.intent.action.GET_CONTENT" />
    </intent>
</queries>

Dylan answer was not required to me, so I still have

<uses-feature
    android:name="android.hardware.camera"
    android:required="false"
    />
3
Badr Bujbara On

For me I was trying to send an email so I needed to set the queries in the Manifest like this:

<queries>
    <intent>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="*" />
    </intent>
</queries>

then send email and check for email clients like this:

        private fun sendEmail(to: Array<String>) {
        val intent = Intent(Intent.ACTION_SENDTO)
        intent.data = Uri.parse("mailto:") // only email apps should handle this
        intent.putExtra(Intent.EXTRA_EMAIL, to)
//        intent.putExtra(Intent.EXTRA_SUBJECT, subject)
        if (intent.resolveActivity(requireContext().packageManager) != null) {
            startActivity(intent)
        }
    }
4
Avital On

For cases when need to start an Activity (not only check if exist), following this advise I removed

if (intent.resolveActivity(packageManager) != null)
                startActivity(intent);

and wrote instaed

try {
     startActivity(intent);
} catch (ActivityNotFoundException e) {
     Toast.makeText(getContext(), getString(R.string.error), Toast.LENGTH_SHORT).show();
}

No need to add any <queries> at the Manifest. Tested on both Api 28 and Api 30.

1
manas.abrol On

You may add QUERY_ALL_PACKAGES permission in AndroidManifest. It doesn't require run-time permission request.

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
0
Stan On

To iterate on Avital's answer, you don't need to declare anything if you want to launch an intent and get to know whether it's been launched:

private fun startIntent(intent: Intent): Boolean {
    return try {
        context.startActivity(intent)
        true
    } catch (e: ActivityNotFoundException) {
        Logger.error("Can't handle intent $intent")
        false
    }
}
0
androidEnthusiast On

In the case you are using the intent action ACTION_INSERT, per documentation, you need to add the intent-filter to the using activity with the action INSERT and with the specified mimeType, to have intent.resolveActivity(view.context.packageManager) != null return true.

<activity ...>
<intent-filter>
    <action android:name="android.intent.action.INSERT" />
    <data android:mimeType="vnd.android.cursor.dir/event" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
2
Eddie Brock On

As the question raised by the user has not set any limitations so simply you can do like this and it will DIRECTLY NAVIGATE to the GMAIL with the EMAIL ID you passed in the INTNET

val emailTo = "[email protected]" // just ex. write the email address you want
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("mailto:$emailTo")
startActivity(intent) // <---- this is work for activity and fragment both
0
Marawan Mamdouh On

Replace

val activityInfo = intent.resolveActivityInfo(packageManager, intent.flags)
    if (activityInfo?.exported == true) {
        startActivity(intent)
    } else {
        Toast.makeText(
            this,
            "No application can handle the link",
            Toast.LENGTH_SHORT
        ).show()
    }

With

if (requireActivity().packageManager.resolveActivity(intent,0) != null){
    startActivity(intent)
}else{
    Toast.makeText(
        this,
        "No application can handle the link",
        Toast.LENGTH_SHORT
    ).show()
}
0
Gianluca Demarinis On

My solution

add this to manifest

<queries>
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
</queries>

use this function

private File create_image() throws IOException {
    @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "img_" + timeStamp;
    File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    return new File(storageDir, imageFileName + ".jpg");
}

use this for Android 10

<application
     android:requestLegacyExternalStorage="true"
     ...

</application>