I've an extension function for opening an intent for my activities:
fun Activity.openIntent(action: String?, type: String?, uri: Uri?) {
Intent()
.apply {
action?.let { this.action = it }
uri?.let { this.data = it }
type?.let { this.type = it }
}
.also { intent ->
packageManager?.let {
if (intent.resolveActivity(it) != null)
startActivity(intent)
else
showToast(R.string.application_not_found)
}
}
}
My targetSdkVersion is 30. It gives me a warning in intent.resolveActivity(it):
Consider adding a queries declaration to your manifest when calling this method.
So What should I do to solve this warning?
The simplest solution is to get rid of resolveActivity(). Replace:
packageManager?.let {
if (intent.resolveActivity(it) != null)
startActivity(intent)
else
showToast(R.string.application_not_found)
}
with:
try {
startActivity(intent)
} catch (ex: ActivityNotFoundException) {
showToast(R.string.application_not_found)
}
This gives you the same result with a bit better performance, and it will get rid of the warning.
Another option would be to add the QUERY_ALL_PACKAGES permission. This may get you banned from the Play Store.
Otherwise, you will need to:
Build a list of every possible openIntent() call that your app may make
Add a <queries> element to your manifest that lists all of those possible Intent structures that you want to be able to use with resolveActivity()
Repeat this process periodically, in case you add new scenarios for openIntent()
So starting Android 11 (i.e, if your app targets Android 11) not all applications will be visible to your application. Some apps are visible by default but in order to access other applications through your application, you will have to declare queries in your manifest else your application will not be able to access them. You can read about that here.
So if your application targets Android 11 and is to access an application that may not be visible by default you will want to add queries for them in the manifest file.
In your case, this warning is not applicable as I believe you are using implicit intents to open other applications. Using implicit intents, other applications can be accessed irrespective of app visibility. If your app target Android 10 or lower you can suppress the warning as all apps are visible by default.
To suppress the lint warning you can either:
Add the suppress annotation, like so:
#SuppressLint("QueryPermissionsNeeded")
fun Activity.openIntent(action: String?, type: String?, uri: Uri?): Activity {
Add the following to your android block in your app module build gradle file:
lintOptions {
ignore "QueryPermissionsNeeded"
}
Replace
if (intent.resolveActivity(it) != null)
with
if (it.resolveActivity(intent, 0) != null)
and the warning will be gone.
From API level 30 package visibility is restricted. So add appropriate query in your AndroidManifest file outside <application> tag.
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>
Related
I'm trying to build an app where one can start audio playback from WearOS.
From what I can tell this requires an app on the phone to obtain a list of possible songs to be played. To do that I want to use the MediaBrowserService, like this example does.
However when I try to reproduce the listing of MediaBrowserServices, I only get the "Bluetooth audio" MediaBrowserService. The example app on the other hand works fine and displays all apps.
Here is the code I'm using:
override fun onStart() {
super.onStart()
val mediaBrowserIntent = Intent(MediaBrowserServiceCompat.SERVICE_INTERFACE)
val packageManager = this.packageManager
val services = packageManager.queryIntentServices(mediaBrowserIntent, PackageManager.GET_RESOLVED_FILTER)
val tv: TextView = findViewById(R.id.helloworld)
val res = StringBuilder()
for(info in services) {
res.appendLine(info.serviceInfo.loadLabel(packageManager).toString())
}
tv.text = res.toString()
}
This is adapted from here.
Do you know what might be causing the issue?
It looks like you need to properly handle the package visibility.
Try to add <queries> section to your manifest file with the correct intent filter signature:
<queries>
<intent>
<action android:name="android.media.browse.MediaBrowserService" />
</intent>
</queries>
The sample app works because it targets SDK 29.
I have small code which makes sure a url is always opened in browser. It is working so far.
Recently, I got message from Google Play Console stating:
If your app requires the QUERY_ALL_PACKAGES permission, you need to
submit the declaration form in Play Console by July 12
I have checked and verified, we don't use QUERY_ALL_PACKAGES in my app. However, we use Explicit Intents and resolveActivity in few places. One of the code (I took it from this place) is:
override fun openInBrowser(url: String) {
val dummyIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
val resolveInfo = context.packageManager.resolveActivity(dummyIntent, PackageManager.MATCH_DEFAULT_ONLY)
val browserPackageName = resolveInfo?.activityInfo?.packageName
val realIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if (browserPackageName != null && browserPackageName.isNotBlank() && browserPackageName != "android") {
realIntent.setPackage(browserPackageName)
}
startActivity(context, realIntent, null)
}
I am not sure if it falls under the category of querying a package. I have gone through this link and as per my understanding I don't need to add either package under queries tag or QUERY_ALL_PACKAGES. I just want to validate my understanding.
The limited app visibility affects the return results of methods that give information about other apps, such as queryIntentActivities(), getPackageInfo(), and getInstalledApplications(). The limited visibility also affects explicit interactions with other apps, such as starting another app's service.
Some packages are still visible automatically. Your app can always see these packages in its queries for other installed apps. To view other packages, declare your app's need for increased package visibility using the element. The use cases page provides examples for common app interaction scenarios.
We are building a native app that displays data which is provided by our clients via a web-based frontend. These data can contain url in https schema. Inside the app these urls should be clickable and open the url inside a browser, if one is istalled. Therefore we would like to check if an app is installed which can handle the intent, and if not display an error message.
The intent looks like this
Intent { act=android.intent.action.VIEW dat=https://wa.me/... }
See below the function to check if an app exists for handling the intent
private fun hasAppToHandleIntent(context: Context, intent: Intent): Boolean {
val packageManager = context.packageManager
val apps = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)
} else {
packageManager.queryIntentActivities(intent, 0)
}
return apps.size != 0
}
As we are targeting android version >= 11 we need to handle the package visibility inside the AndroidManifest and added the following query
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
This approach worked nicely until one of our clients put in a url which in the end leads to a deeplink into WhatsApp. It had the following format https://wa.me/123456. This url yields different results for the hasAppToHandleIntent() function on different devices.
On a Google Pixel 5 (running Android 12 ) and an emulator (running Android 10) the queryIntentActivities() function returns a list with installed browsers to handle the intent. On a Samsung Galaxy S20 however queryIntentActivities() returns an empty list, even though multiple browsers and WhatsApp app are installed.
What are we missing here? Why does queryIntentActivities() return an empty list?
It seems like the url with format https://wa.me/123456 is, in some cases and devices, automatically treated as a deeplink into WhatsApp and NOT handled as a normal url. I did not find any documentation or information as to why this happens, so if anybody could provide additional infos I would very much appreciate it.
Following approaches would be possible:
Add a query for WhatsApp inside the AndroidManifest
<queries>
<package android:name="com.whatsapp" />
<package android:name="com.whatsapp.w4b" />
</queries>
As there could also be other deeplinks which could lead to the same problem. Adding all of them was no option for us
Declare that your app needs to query all installed apps, not just the ones declared by your queries. This approach was not tested but should work. See https://developer.android.com/training/package-visibility/declaring#all-apps for further information. As stated there this way is not recommended and we did not use this approach either.
Our workaround approach first checks if queryIntentActivities() returns an empty list. If so we check if an app exists which can handle a normal https url.
private fun hasAppToHandleIntent(context: Context, intent: Intent): Boolean {
val packageManager = context.packageManager
val apps = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)
} else {
packageManager.queryIntentActivities(intent, 0)
}
if (apps.size == 0 && intent.dataString?.startsWith("http") == true) {
val uri = Uri.parse("https://www.test.com")
val urlIntent = Intent(Intent.ACTION_VIEW).apply { data = uri }
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
packageManager.queryIntentActivities(urlIntent, PackageManager.MATCH_ALL).size != 0
} else {
packageManager.queryIntentActivities(urlIntent, 0).size != 0
}
}
return apps.size != 0
}
I've an extension function for opening an intent for my activities:
fun Activity.openIntent(action: String?, type: String?, uri: Uri?) {
Intent()
.apply {
action?.let { this.action = it }
uri?.let { this.data = it }
type?.let { this.type = it }
}
.also { intent ->
packageManager?.let {
if (intent.resolveActivity(it) != null)
startActivity(intent)
else
showToast(R.string.application_not_found)
}
}
}
My targetSdkVersion is 30. It gives me a warning in intent.resolveActivity(it):
Consider adding a queries declaration to your manifest when calling this method.
So What should I do to solve this warning?
The simplest solution is to get rid of resolveActivity(). Replace:
packageManager?.let {
if (intent.resolveActivity(it) != null)
startActivity(intent)
else
showToast(R.string.application_not_found)
}
with:
try {
startActivity(intent)
} catch (ex: ActivityNotFoundException) {
showToast(R.string.application_not_found)
}
This gives you the same result with a bit better performance, and it will get rid of the warning.
Another option would be to add the QUERY_ALL_PACKAGES permission. This may get you banned from the Play Store.
Otherwise, you will need to:
Build a list of every possible openIntent() call that your app may make
Add a <queries> element to your manifest that lists all of those possible Intent structures that you want to be able to use with resolveActivity()
Repeat this process periodically, in case you add new scenarios for openIntent()
So starting Android 11 (i.e, if your app targets Android 11) not all applications will be visible to your application. Some apps are visible by default but in order to access other applications through your application, you will have to declare queries in your manifest else your application will not be able to access them. You can read about that here.
So if your application targets Android 11 and is to access an application that may not be visible by default you will want to add queries for them in the manifest file.
In your case, this warning is not applicable as I believe you are using implicit intents to open other applications. Using implicit intents, other applications can be accessed irrespective of app visibility. If your app target Android 10 or lower you can suppress the warning as all apps are visible by default.
To suppress the lint warning you can either:
Add the suppress annotation, like so:
#SuppressLint("QueryPermissionsNeeded")
fun Activity.openIntent(action: String?, type: String?, uri: Uri?): Activity {
Add the following to your android block in your app module build gradle file:
lintOptions {
ignore "QueryPermissionsNeeded"
}
Replace
if (intent.resolveActivity(it) != null)
with
if (it.resolveActivity(intent, 0) != null)
and the warning will be gone.
From API level 30 package visibility is restricted. So add appropriate query in your AndroidManifest file outside <application> tag.
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>
I have a android app where I want to run other apps. If these apps are not installed already, I would like to open PlayStore to let the user install them:
var info : ApplicationInfo? = null
try {
info = pm.getApplicationInfo(packageName, 0) as ApplicationInfo
} catch (e: PackageManager.NameNotFoundException){
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("market://details?id=$packageName")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
if (info != null) {
val intent = pm.getintentForPackage(packageName) as Intent
context.startActivity(intent)
}
This snippet works so far good for API 26 (Android 8) for instance. But for API 30 (Android 11) the getApplicationInfo() throws a NameNotFoundException always, even when I already installed the app.
Whats wrong here?
On Android 11, methods on PackageManager like getApplicationInfo() no longer work by default. You need to either:
Not call them, or
Add a <queries> element to your manifest, whitelisting the third-party apps that you wish to find, or
Request the MANAGE_ALL_APPS permission (and run the risk of your app being banned from the Play Store)
In your case, the <queries> element is probably a safe choice, assuming that your list of application IDs that you are looking for is knowable at compile time.