Android 7.1: App Shortcuts

TL;DR: We are going to talk about a new Android 7.1 feature, the so called App Shortcuts. How it looks like, how the API works, good-to-knows and potential pitfalls.


Note: The content of this article is identical to this Android Budapest Meetup talk:

What does an app shortcut do?

If your app targets Android 7.1 (API level 25) or higher, you can define shortcuts to specific actions in your app. These shortcuts can be displayed in a supported launcher. Shortcuts let your users quickly start common or recommended tasks within your app.” via developer.android.com

In real life it looks like this:

App Shortcuts in the Prezi Viewer app

Before we move forward

“…If your app targets Android 7.1 (API level 25) or higher…”, this means that we can ship this cool feature to ~0.4% of the users. The documentation also says that “…These shortcuts can be displayed in a supported launcher…”. Right now it’s only supported in the Pixel, Nova, and Now launcher. That means if we invest time into this implementation we are targeting less than 0.4 % of our entire user base.

Now you have to decide: Either you continue reading, or you “let it grow”, finish reading, and come back when you can release it to a decent amount of users.

I’m glad that you are still here, because that’s what we, Developers / Engineers are suppose to do: we have to build the future. Just imagine how the mobile world would look like, if the Developers 3 months after the Lollipop release said: “Nah, I won’t redesign my app into this Material thingy, since it’s only on 5.0 or above, which is less than 0.1%”


The API

The Android gives us a nice, clean API. You have a basic, data class with a Builder pattern, this is the ShortcutInfo class. When you ask for a shortcut at runtime, you’ll always get back a list of these.

The notable ShortcutInfo fields are the following:
 — Id: Unique string id for a shortcut.
 — Long label: Max 25 character long string (visible when the user open the shortcut list).
 —Short label: Max 10 character long string (visible when the shortcut pinned).
 —Rank: Used to order the shortcuts in the list.
 —DisabledMessage: Appear in a toast if the shortcut disabled and the user starts it.
 — Intent(s): The defined action(s).
 — Icon: A bitmap icon. It won’t be rounded by the system.
 — …

The other important class is the main entry point, a simple, old fashioned Manager class, the ShortcutManager (get it via getSystemService). Through the ShortcutManager you can get, add, remove, disable, and update the shortcuts. Unfortunately there is no Compat class right now, and we have to deal with the ugly android version checking by ourselves.

Shortcut types

As always in Android, you can define them in XML at install time or in Java at runtime. You can define shortcuts per (launcher) Activity.

Static shortcuts

These are the ones which defined in XML. The trick here is that you cannot modify these shortcuts at runtime. That means you cannot change the text, or the icon of the shortcut. You can’t even disable it.

Shortcut definition in xml:

<shortcuts xmlns:android="...">
<shortcut
android:shortcutId="my_shortcut"
android:enabled="true"
android:icon="@drawable/icon"
android:shortcutShortLabel="@string/shortcut_short_label"
android:shortcutLongLabel="@string/shortcut_long_label"
android:shortcutDisabledMessage="@string/disabled_message">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="com.example.myapplication"
android:targetClass="com.example.Activity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<!-- ... -->
</shortcuts>

Set the shortcuts for an Activity:

<activity android:name="Main">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
  <!-- Set the shortcuts -->  
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>

If you get the shortcuts runtime you can check whether they’re static or not with the isDeclaredInMaifest() method.

Note: The static shortcuts will be restored if “allowBackup”=”true” in the Manifest.

Dynamic shortcuts

You can add/set them only at runtime, through the ShortcutManager. add|setDynamicShortcuts(…) method:

ShortcutInfo shortcut = new ShortcutInfo.Builder(context, “id1”)
.setShortLabel(“Web site”)
.setLongLabel(“Open the web site”)
.setIcon(Icon.createWithResource(context, R.drawable.icon_website))
.setIntent(new Intent(Intent.ACTION_VIEW,
Uri.parse(“https://www.mysite.example.com/")))
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));

You can check them with the isDynamic() method.

Note: The dynamic shortcuts are not restored.

Pinned shortcuts

Although it’s not an official type, I personally think it’s something worth separating. This pinned shortcut is created by the user, when he/she drags a shortcut and pins it to the home screen (the system will create a copy of the original shortcut). From code, you cannot create it (It’s slightly different in the new O version), but you can get the list of the pinned shortcuts. Also for the pinned shortcuts the isPinned() method will return true.

Note #1: You cannot delete a pinned shortcut, you can only disable it with the help of the ShortcutManager.disableShortcuts(…) method.
Note #2: A pinned shortcut is also restored, but the shortcut’s icon is not. You have to update the shortcut to set the image.

With this knowledge you can easily add shortcuts to your app, and provide a new way for the users to interact with your app. Now we are going to show the things what we learned from implementing it in the Prezi Viewer app.


Good-to-knows

These are the tips and pitfalls what we found during the implementation. It’s purely a personal list, you may find other things (we are eager to hear about them!), or you might disagree with the ones we consider “pitfalls”, in which case, tell us too! The order of the list is random.

Tip #0: Let the user know

Since it’s not a well known feature, you should indicate to the user that your app has this feature. Obviously there are a lot of ways to do it: with an onboarding, or an annoying dialog at app start, or a notification, … We decided to use the new round icon (also introduced in API 25) to indicate the presence of the app shortcuts.

< API 25 on the left without shortcuts, and ≥ API 25 on the right with shortcuts.

We still need time to collect data and confirm that this is a useful way to show the feature’s presence, but for now we’ll stick with it.

Tip #1: Report shortcut use

The official documentation says, that you should report a shortcut’s action use even if it’s not started from an actual shortcut (So when the user reach a point in your app which is also reachable from a shortcut). You can use the ShortcutManager.reportShortcutUsed(String id) method. This information will be used to build a prediction model for the launcher, and give suggestions to the user (based on time, location, …).

Tip #2: Create Intent back stack if necessary

You can define multiple Intents to create a back stack for a shortcut with the builder.setIntents(Intent[] intents) method. The Android will start them for you, and the last Intent in the array will be the visible to the user. It’s going to call the Context. startActivties(Intent[] intents).

Tip #3: Don’t use Parcelable in the Intent

You can set Intent(s) for the shortcut, but you cannot add Parcelable as extra, because the shortcut uses PersistableBundle (to persist it across boots and for backup).
The PersistableBundle only accepts the following types:
 — Integer and int[],
 — Double and double[],
 — Long and long[],
 — String and String[],
 — Boolean and boolean[],
 — null

Tip #5: Use the proper design guidelines

Along with the feature we got a pretty decent design guideline. Look into it, it’s really great.

Tip #4: 5 = 4

Your app has a limit on how many shortcuts you can set (for static + dynamic per Activity). You can get this limit with the ShortcutManager. getMaxShortcutPerActivity().
If you set more than this, you’ll get a RuntimeException when you add/set the shortcuts. Although this will return 5, the launcher will show only 4 of them (The issue already reported here).

Tip #6: You can set the shortcut’s order with the rank

The shortcuts’ order is computed with the following logic:
First, the static shortcuts in increasing rank order, then dynamic shortcuts in increasing rank (the rank is auto-adjusted, which means you can insert between existing items).

Tip #7: Not so round icons

The design guideline specifically says that you should always use circle images, but the rounding should be done on your app’s side. Fortunately if you use Picasso/Glide/Fresco it’s really easy.

Tip #8: Limitations in background

You have a rate-limit (check it with ShortcutManager. isRateLimitingActive()), how many times you can call the set/add/update (dynamic) shortcuts from the background (those method will return false if the limit reached). It will reset when the app comes to foreground, the system locale changes, or an inline reply action happened.

Note: Use CountdownLatch (or something similar) to batch image downloading to not hit the rate-limit with the individual updates.

Tip #9: Listen for locale changes

Listen for ACTION_LOCALE_CHANGED action, because you should update your shortcuts when it happens. Also the rate limit will reset to let you update in the background.

That’s it… , now you know everything we know about the awesome App Shortcuts feature.


Hello There, thanks for reading this far. If you have any questions or ideas, please don’t hesitate to contact me. You can reach me on Twitter or leave a comment here.