Exploring Android Nougat 7.1 App Shortcuts

Google has brought Android Nougat to its second iteration with the 7.1 version (API 25). This one is not a minor release - as a matter of fact it bundles some interesting features under the hood. One of these extra features is App Shortcuts. This post explores what they are, how they work, and how you can implement them.

The end result looks like this:
app-shortcuts-final

If you want to go through a step by step guide, please read on.

What are App Shortcuts and why would you need them?

App Shortcuts are a means of exposing your application’s common actions or tasks on the user’s home screen. Your users can reveal the shortcuts by long-pressing the app's launcher icon. From a technical perspective, App Shortcuts are a simple and quick way of firing your application’s Intents1.

They are of two types:

By exposing your common tasks, you'll make it easier for your users to quickly get back into specific parts of your application without the need of additional navigation.

Adding App Shortcuts

Adding Shortcuts to your app is pretty straightforward. Let's start with creating a simple static shortcut2.

Static shortcuts

This example assumes you already have a project set up in Android Studio. Navigate to your AndroidManifest.xml and add the following meta-data tag to your main activity:

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  package="com.catinean.appshortcutsdemo">


  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />


        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>


      <meta-data
        android:name="android.app.shortcuts"
        android:resource="@xml/shortcuts" />
    </activity>
  </application>


</manifest>  

In the meta-data tag, the android:resource key corresponds to a resource defined in your res/xml/shortcuts.xml. Here you need to define all of your static shortcuts. Let's add one that will open a certain activity from your app. In the example below I've created a dummy StaticShortcutActivity:

<?xml version="1.0" encoding="utf-8"?>  
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">  
  <shortcut
    android:enabled="true"
    android:icon="@drawable/ic_static_shortcut"
    android:shortcutDisabledMessage="@string/static_shortcut_disabled_message"
    android:shortcutId="static"
    android:shortcutLongLabel="@string/static_shortcut_long_label"
    android:shortcutShortLabel="@string/static_shortcut_short_label">
    <intent
      android:action="android.intent.action.VIEW"
      android:targetClass="com.catinean.appshortcutsdemo.StaticShortcutActivity"
      android:targetPackage="com.catinean.appshortcutsdemo" />
  </shortcut>
</shortcuts>  

You can see that the root tag of this file is <shortcuts>, which can hold multiple <shortcut> blocks. Each of them, as you may have guessed, represents a static shortcut. Here, the following properties can be set on one shortcut:

Here’s how this shortcut would appear to a user of your app:

app_shortcut_static


Nice and easy, but if you implement this you may notice that upon pressing back the user is taken back to the home screen. What about instead navigating ‘up’ within the app? To do so, we can add multiple intent tags under the shortcut ones we previously created:

<?xml version="1.0" encoding="utf-8"?>  
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">  
  <shortcut
  ...>
    <intent
      android:action="android.intent.action.MAIN"
      android:targetClass="com.catinean.appshortcutsdemo.MainActivity"
      android:targetPackage="com.catinean.appshortcutsdemo" />
    <intent
      android:action="android.intent.action.VIEW"
      android:targetClass="com.catinean.appshortcutsdemo.StaticShortcutActivity"
      android:targetPackage="com.catinean.appshortcutsdemo" />
  </shortcut>
</shortcuts>  

Notice how we added an extra <intent> before the one that we had, pointing to the MainActivity. This will create a back stack of Intents, the last one being the one opened by the shortcut. In our case the back stack looks like MainActivity -> Static ShortcutActivity, so when pressing back the user is taken into the MainActivity:

app_shortcut_static_back_stack

Adding static shortcuts is pretty easy. Let's move on defining some dynamic ones.

Dynamic shortcuts

As their name suggests, dynamic shortcuts can be modified at runtime without the need of re-deploying your app. As you may have guessed, these are not defined through a static resource (shortcuts.xml) like the static ones, but are created in code.

Let's add our first dynamic shortcut! In order to do so, you will have to make use of the ShortcutManager and the ShortcutInfo.Builder. I'll be constructing the first dynamic shortcut in my MainActivity.#onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {


    ...


    ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);


    ShortcutInfo webShortcut = new ShortcutInfo.Builder(this, "shortcut_web")
            .setShortLabel("novoda.com")
            .setLongLabel("Open novoda.com web site")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_dynamic_shortcut))
            .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("https://novoda.com")))
            .build();


    shortcutManager.setDynamicShortcuts(Collections.singletonList(webShortcut));
}

In the example above we acquire the shortcutManager and construct a ShortcutInfo. By using the ShortcutInfo.Builder we can set various properties for the shortcut we want to create. All the builder methods we use above correspond to the same properties used for a static shortcut, so I’ll skip e explaining them again. However, one property that is a bit obscure is the id of the shortcut which is defined in the StaticInfo.Builder constructor as second parameter - shortcut_web. In the example above I've defined the Intent being one that will open my website. Finally, I set the dynamic shortcut on the ShortcutManager. Let's see now how our shortcuts look now:

app_shortcut_dynamic_website


Great! Now we have 2 app shortcuts in our app - one static and one dynamic.

Let's add another one that will point to an activity inside the app and see how we can create a back stack for it:

@Override
protected void onCreate(Bundle savedInstanceState) {


    ...


    ShortcutInfo dynamicShortcut = new ShortcutInfo.Builder(this, "shortcut_dynamic")
            .setShortLabel("Dynamic")
            .setLongLabel("Open dynamic shortcut")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_dynamic_shortcut_2))
            .setIntents(
                    new Intent[]{
                            new Intent(Intent.ACTION_MAIN, Uri.EMPTY, this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
                            new Intent(DynamicShortcutActivity.ACTION)
                    })
            .build();


    shortcutManager.setDynamicShortcuts(Arrays.asList(webShortcut, dynamicShortcut));
}

You can see now that we now setIntents() on the builder in order to build a back stack:

<activity  
      android:name=".DynamicShortcutActivity"
      android:label="Dynamic shortcut activity">
      <intent-filter>
        <action android:name="com.catinean.appshortcutsdemo.OPEN_DYNAMIC_SHORTCUT" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
</activity>  

By declaring this array of intents in this order, we ensure that when the user presses back after opening DynamicShortcutActivity through the shortcut we created, the MainActivity will be opened.

Let's see how they look like:

app_shortcut_dynamic_activity

Shortcut ordering

Now that we have 1 static shortcut and 2 dynamic ones, how can we specify a custom order for them? If we take a closer look at the ShortcutInfo.Builder methods, one in particular gives us a clue: setRank(int). By setting a custom rank to a dynamic shortcut we can control the order they appear when revealed: the higher the rank, the most top the shortcut goes.

As an example, say we want shortcut number 2 (novoda.com) to sit at the top. We can dynamically change the ranks of the already added dynamic shortcuts. Let's do this when pressing a button from MainActivity:

findViewById(R.id.main_rank_button).setOnClickListener(new View.OnClickListener() {


      @Override
      public void onClick(View view) {
          ShortcutInfo webShortcut = new ShortcutInfo.Builder(MainActivity.this, "shortcut_web")
                  .setRank(1)
                  .build();


          ShortcutInfo dynamicShortcut = new ShortcutInfo.Builder(MainActivity.this, "shortcut_dynamic")
                  .setRank(0)
                  .build();


          shortcutManager.updateShortcuts(Arrays.asList(webShortcut, dynamicShortcut));
      }
});

In the click listener of the button we create a new ShortcutInfo for each shortcut we have previously added with the same IDs, but now we set a higher rank to the shortcut_web one and a lower one for shortcut_dynamic. Finally, we use the updateShortcuts(List<ShortcutInfo>) method of the ShortcutManager to update the shortcuts with the newly set ranks:

app_shortcut_ranks

You can see from the above gif that the static shortcut sits at the bottom of the list. One thing to note: you cannot change the rank of a static shortcut. They will be shown in the order they're defined in the shortcuts.xml file. Since we have only one static shortcut, it has the default rank of 0 which cannot be changed.

Extra bits

If we take a closer look at the setShortLabel(CharSequence) method of ShortcutInfo.Builder, we can see that it accepts a CharSequence as a parameter. What does this mean? Well, it means that we can play around a little with it as we can attach custom spans to it.

Let's say we want to change its colour to red when pressing the above created button. We can create a SpannableStringBuilder and set to it a ForegroundColorSpan with the desired colour and then pass the spannableStringBuilder as a shortLabel (as SpannableStringBuilder implements CharSequence):

findViewById(R.id.main_rank_button).setOnClickListener(new View.OnClickListener() {


    @Override
    public void onClick(View view) {


        ForegroundColorSpan colorSpan = new ForegroundColorSpan(getResources().getColor(android.R.color.holo_red_dark, getTheme()));
        String label = "novoda.com";
        SpannableStringBuilder colouredLabel = new SpannableStringBuilder(label);
        colouredLabel.setSpan(colorSpan, 0, label.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);


        ShortcutInfo webShortcut = new ShortcutInfo.Builder(MainActivity.this, "shortcut_web")
                .setShortLabel(colouredLabel)
                .setRank(1)
                .build();


        ...
    }
});

app-shortcuts-spans

Wrapping up

You can checkout this blog post's sample app on Github

This is a cross-post from https://catinean.com/2016/10/20/exploring-android-nougat-7-1-app-shortcuts/


  1. You'll have to use an Android Nougat 7.1 device with a launcher that supports shortcuts (like the Pixel launcher or the Now launcher)

  2. You can only have up to 5 shortcuts for one app

About Novoda

We plan, design, and develop the world’s most desirable Android products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact