Retrieve and launch app shortcuts on Android without being a launcher
What are app shortcuts?
Introduced in Android 7.1 (API level 25), app shortcuts (not to be confused with the older “launcher shortcuts”) are a handy feature that lets users perform key actions in an app directly from the launcher. Along with them came a set of new APIs for developers to query and launch shortcuts:
- LauncherApps.getShortcuts,
- LauncherApps.getShortcutIconDrawable,
- LauncherApps.startShortcut,
- etc.
But there’s a catch! As their location suggests, they are only available if the calling app is the curent launcher or voice interaction service, in which case LauncherApp.hasShortcutHostPermission() returns true.
Is there any way for non-launcher apps to interact with shortcuts? That’s what I was wondering when developing App Search — a lightweight utility that lets you quickly search and launch apps. Having the ability to display and open shortcuts would make it even more convenient.
How to retrieve and launch app shortcuts
Checking if an app has shortcuts
Static shortcuts are defined in an XML resource file — let’s call it shortcuts.xml
— which is in turn referenced in a <meta-data>
element belonging to a launcher activity:
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
To read this file, you’ll need to use the PackageManager.GET_META_DATA
flag while querying activities from PackageManager
. The below piece of code queries all launcher activities on the device, then check if each activity has any associated shortcuts:
// Narrows down results to launcher activities
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHERS)
val resolveInfoList = packageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA)
for (resolveInfo in resolveInfoList) {
// Remember "android.app.shortcuts" from before?
val metadata = resolveInfo.activityInfo.loadXmlMetaData(packageManager, "android.app.shortcuts")
if (metadata != null) {
// Start retrieving shortcuts
}
}
Parsing shortcuts.xml
loadXmlMetaData()
returns an XmlResourceParser
object, which can be used to parse shortcuts.xml
. The file looks similar to this:
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="compose"
android:enabled="true"
android:icon="@drawable/compose_icon"
android:shortcutShortLabel="@string/compose_shortcut_short_label1"
android:shortcutLongLabel="@string/compose_shortcut_long_label1"
android:shortcutDisabledMessage="@string/compose_disabled_message1">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="com.example.myapplication"
android:targetClass="com.example.myapplication.ComposeActivity" />
<!-- If your shortcut is associated with multiple intents, include them
here. The last intent in the list determines what the user sees when
they launch this shortcut. -->
<categories android:name="android.shortcut.conversation" />
<capability-binding android:key="actions.intent.CREATE_MESSAGE" />
</shortcut>
<!-- Specify more shortcuts here. -->
</shortcuts>
Using XmlPullParser
(which XmlResourceParser
is a subclass of) is not difficult — just call getEventType()
, which returns one in five event states; use get…()
methods to extract data; and call next()
when done. The official Android documentation for XmlPullParser
has a good tutorial on it.
The shortcuts.xml
parsing code is a bit too long to be posted here, as there is more to do than just looping through XML events — for example, keeping track of the current shortcuts, fetching icons and labels, and populating shortcut intents. You can check it out here, or clone the whole demo app.
Some tips:
- To retrieve string attributes, such as
shortcutId
, usegetAttributeValue
. - To retrieve resource ID attributes, such as
shortcutShortLabel
, usegetAttributeResourceValue
. - The two methods above requires passing in an XML namespace, which should be
http://schemas.android.com/apk/res/android
(theandroid:
namespace). - However, some apps (most notably Chrome) do not use namespaces in their
shortcuts.xml
. If getting an attribute with theandroid:
namespace doesn’t work, try again withnull
. - To get the actual strings and icon drawables, call
PackageManager.getResourceForApplication
, passing in the package name of the app that you’re getting shortcuts from. This gives you the app’sResources
, which you can callgetString
orgetDrawable
on. - Beside the
targetClass
attribute, some Google apps also use the undocumentedtargetActivity
attribute. I had to replace $ (dollar signs) in its value with _ (underscores) for the activity to launch — don’t ask me why!
That’s it! Thanks to this workaround, your app can interact with shortcuts without being a launcher. Here’s my demo app in action:
But…
There are considerable limitations with this method:
- Some shortcuts point to non-exported activities that can’t be launched by other apps. I was very disappointed to find out that this was the case with quite a few popular apps, such as Chrome or Gmail. Android will throw a
SecurityException
if you try to launch them.
- Dynamic shortcuts are not declared in the manifest and therefore can’t be retrieved.
- Lastly, shortcuts were added on Android 7.1, and some of the attributes used for declaring shortcuts were only introduced in that version of Android. They include
shortcutId
,shortcutShortLabel
,shortcutLongLabel
, andshortcutDisabledMessage
. As a result, on older Android versions, you won’t be able to show a shortcut’s actual ID or label.
Conclusion
There is a workaround for getting another app’s static shortcut when you are not a launcher: parsing the shortcuts.xml
file declared in the app’s manifest. However, many shortcuts target non-exported activites, which limits its usability.
In the end, I implemented the ability to show and pin shortcuts in my app. I hope it will prove useful, and I hope you’ve found this article helpful as well.
References
- The official way to interact with shortcuts: https://stackoverflow.com/a/48488546/6244733
- My demo app: https://gitlab.com/abn-volk/app-shortcut-demo