121

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?

CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • 7
    Assuming that you're targetting API level 30, that appears to be due to this: [Package visibility in Android 11](https://developer.android.com/preview/privacy/package-visibility). Indeed, when I test your first snippet with an appropriate `` element in the manifest, it works as expected. If you'd rather not include such a ``, then you could just stick with the `try`-`catch`. – Mike M. Jun 23 '20 at 13:54
  • @MikeM., thanks! Could you post it as an answer? I will later test it. – CoolMind Jun 23 '20 at 15:45

11 Answers11

157

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.

Mike M.
  • 38,532
  • 8
  • 99
  • 95
  • 12
    Worked for me except there's a warning: `Element category is not allowed here`. – Westy92 Sep 02 '20 at 16:28
  • @Westy92, Google removed this warning. – CoolMind Dec 14 '20 at 09:18
  • 1
    Could you explain why the BROWSABLE category is recommended for this? Things seem to work the same without it – yuval Dec 17 '20 at 03:22
  • 10
    Would be nice if there was some sort of Warning/Error thrown letting you know you need to add that in the manifest – lasec0203 Mar 03 '21 at 06:20
  • probably you need this: https://stackoverflow.com/questions/62969917/how-to-fix-unexpected-element-queries-found-in-manifest-error – AlexS Jun 01 '21 at 12:32
  • @Westy92 My guess is belongs only to , we don't need that for I searched for a few examples in Android official docs, I see that it is listed only under https://developer.android.com/guide/components/intents-filters#ExampleFilters – Madhan Nov 01 '21 at 19:56
  • I have needed queries in the manifest and it still doesn't work https://stackoverflow.com/questions/73434747/a-imdb-link-wont-open-in-official-imdb-app/73434822#73434822 – user924 Aug 21 '22 at 13:25
116

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"
    />
CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • Right answer, Thanks, Now working fine in Android 11 One plus device. – Amardeepvijay Dec 02 '21 at 06:06
  • Do here is any difference in or ? what if I may access both https and http in different button of my app? I need to add two or in one tag? – Raii Jun 25 '22 at 09:54
  • 1
    @Raii, currently I don't know. I used `http` for both cases. Do you want to know if a device has a browser? Or do you want to launch your application by a link? In second case you are to learn Deep Linking. – CoolMind Jun 26 '22 at 18:38
  • @CoolMind I just want to go to the Google Play Shop for my app, I have used some lines of code (the shop link is https) for years but recently I find the warning and I see this post I need to add ... to handle this and I see one answer saying http and another answer saying https @@ – Raii Jun 28 '22 at 00:50
  • 1
    @Raii, thanks! You can use `https` only for `https://` addresses. Currently it's enough for your task. If you set `http` instead of `https` you will open both `https://` and `http://` domains. So, it's up to you, what you prefer more. – CoolMind Jun 28 '22 at 10:15
42

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)
        }
    }
fullmoon
  • 8,030
  • 5
  • 43
  • 58
  • 14
    This is not mentioned on androids official link , which teaches how to send emails https://developer.android.com/guide/components/intents-common#Email. I hate this platform often , It makes me want to pull all my hair out . – Muhammad Ahmed AbuTalib Jun 23 '21 at 16:06
  • 3
    Totally agree @muhammad-ahmed-abutalib . For me, `` worked. – Favolas Jul 19 '21 at 08:10
  • This solution works for me, however, if I go back from the email app, then I need to tap the button twice to open the activity, even though "intent.resolveActivity(context.packageManager)" is not null. – FabioR Mar 02 '23 at 20:59
31

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.

Avital
  • 549
  • 5
  • 15
  • This is a case when you have to start an activity. But there may be a situation when you need to know if there are applications, but not start them. – CoolMind Jan 21 '21 at 13:36
  • Thanks! I didn't notice that. I Edit the answer – Avital Jan 21 '21 at 18:14
  • 1
    @CoolMind You could have mentioned an example. I only can imagine to hide some controls if there is no app installed to handle it. For example: I don't need a photo button if there's no camera app. – The incredible Jan Sep 14 '21 at 12:35
  • @TheincredibleJan, yes. If you want to have an example, there may be situations when you have to choose whether to start an application or handle yourself. For instance, an application will show one screen if there are registered applications of your company in this device, or usual screen in other cases. Or your application can be opened by Deep Link and edit e-mail message, then send it via e-mail client. But if there are no e-mail clients, it should open another screen or close itself, for instance. I didn't try these schemes. – CoolMind Sep 14 '21 at 13:06
6

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
    }
}
Stan
  • 4,169
  • 2
  • 31
  • 39
1

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>
Dylan
  • 375
  • 1
  • 2
  • 12
1

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"/>
manas.abrol
  • 885
  • 8
  • 6
  • 6
    Yes, but Google Play [restricts](https://support.google.com/googleplay/android-developer/answer/10158779) the use of high risk or sensitive permissions, including the QUERY_ALL_PACKAGES permission, which gives visibility into the inventory of installed apps on a given device. – CoolMind May 27 '21 at 13:46
1

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>
Gianluca Demarinis
  • 1,964
  • 2
  • 15
  • 21
0

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>
androidEnthusiast
  • 1,140
  • 1
  • 12
  • 21
0

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 = "user@gmail.com" // 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
Eddie Brock
  • 158
  • 1
  • 8
  • 1
    I suppose, it will work if a device contains email applications. If not, it will crash. – CoolMind Apr 25 '22 at 07:26
  • Yes we can assume that but nowadays no device in android have seen without an email or Gmail app or else we can just use the above answers as well as we can use try catch – Eddie Brock Apr 25 '22 at 08:33
0

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()
}
Marawan Mamdouh
  • 584
  • 1
  • 6
  • 15