App Shortcuts事始め

  • 16
    いいね
  • 0
    コメント

Androidその2 Advent Calendar 2016の4日目の記事です。
Android 7.1のAppShortcutsを紹介します。

App Shortcutsとは

App ShortcutsとはAndroid 7.1で追加されたアプリ内機能のショートカットを作成できる機能です。
アプリアイコンを長押しすると表示されるショートカットからアプリ内の各機能にアクセスできます。
ショートカットはコピーしてランチャーにピン止めしておくこともできます。
利用頻度が高い機能をショートカットに追加しておくと機能へ即座にアクセスできるようになって便利です。

ショートカット一覧 ショートカットのピン留め
app-shortcuts-list.png app-shortcuts-pin.png

ちなみにGoogleのアプリ以外だとTwitter、Talon(Tiwtterクライアント)等はすでに対応しているのでGooglePlayから落として動作をみてみると良いかもしれません。

App Shortcutsの種類

ショートカットには2つの種類があります。

Static Shortcuts
静的なショートカットでxmlにショートカットの設定を記述しショートカットを表示します。
動的にショートカットの更新や削除等は出来ず、変更するにはアプリのアップデートが必要です。
(動的に削除しようとすると落ちます。。)

Dynamic Shortcuts
動的なショートカットでStaticShortcutsと違いコード上からショートカットの追加・削除・更新等が可能です。
動的に追加できるのでユーザーがよくアクセスするページをショートカットに追加したり、ユーザーにショートカットを選ばせたりいろいろな使い方ができそうです。

Static Shortcutsを実装する

ショートカットの設定ファイルを用意

表示するショートカットの設定を記述したxmlファイルを res/xml に配置します。
shortcuts内に表示するショートカットを記述していきます。

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

    <!-- ショートカットのID・アイコン・文言等を設定 -->
    <shortcut
        android:icon="@mipmap/ic_launcher"
        android:shortcutId="first_shortcut"
        android:shortcutShortLabel="@string/short_label">
        <!-- 遷移先 -->
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.kuwapp.myshortcutsample"
            android:targetClass="com.kuwapp.myshortcutsample.MainActivity" />
    </shortcut>

</shortcuts>

AndroidManifest.xmlにショートカットの設定ファイルを指定

intent-filterにandroid.intent.action.MAINandroid.intent.category.LAUNCHERを持つActivityのmeta-data要素にショートカットの設定ファイルを指定します。

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

    <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" />
            </intent-filter>
            <!-- 設定ファイルを指定する -->
            <meta-data android:name="android.app.shortcuts"
                android:resource="@xml/shortcuts" />
        </activity>
    </application>

</manifest>

これだけでショートカットが表示されるはずです。

shortcut要素に記述できる属性

属性 説明
android:shortcutId shortcutを識別するためのID 必須
android:enabled 有効、無効のフラグ 初期値はtrue
android:shortcutShortLabel ピンされたアイコンのタイトル文言
android:shortcutLongLabel ランチャーアイコン長押し時に表示されるショートカットの文言 (定義されていない場合ShortLabelが使われる)
android:shortcutDisabledMessage 無効なピン留めされたショートカットがタップされたタイミングで表示される文言

Dynamic Shortcutsを実装する

DynamicShortcutsの追加

ShortcutManagerクラスのaddDynamicShortcutsまたはsetDynamicShortcutsメソッドを使用します。
どちらのメソッドでもDynamicShortcutsは追加できますが、違いとしてaddDynamicShortcutsは既存のDynamicShortcutsのリストに新たなDynamicShortcutsを追加するのに対してsetDynamicShortcutsは既存のDynamicShortcutsのリストを新しいDynamicShortcutsに置き換えます。

Intent intent = new Intent(getApplicationContext(), MainActivity.class);
// Actionを設定しないと落ちる
intent.setAction(Intent.ACTION_DEFAULT);
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, id)
        .setShortLabel(getString(R.string.short_label))
        .setIntent(intent)
        .build();
shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcutInfo));

DynamicShortcutsの削除

ShortcutManagerクラスのremoveAllDynamicShortcutsまたはremoveDynamicShortcutsメソッドを使用します。
メソッド名から推測できる通り、removeAllDynamicShortcutsは全てのDynamicShortcutsを削除し、removeDynamicShortcutsは単一のDynamicShortcutsを削除します。
removeDynamicShortcutsの引数には削除するDynamicShortcutsのIDを指定します。

// 削除するDynamicShortcutのIDを指定
shortcutManager.removeDynamicShortcuts(removeShortcutIdList);

DynamicShortcutsの更新

ShortcutManagerクラスのupdateShortcutsメソッドを使用します。

// 更新するShortcutと同一のIDを指定
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, id)
        .setShortLabel(getString(R.string.short_label))
        .setIntent(intent)
        .build();
shortcutManager.updateShortcuts(Collections.singletonList(shortcutInfo));

ショートカットを無効にする

ショートカットを無効にした場合、一覧から表示されなくなり、ピン留していたアイコンはグレーアウトしてタップしても動作しません。
タップ時はdisabledMessageで指定した文言がトーストで表示されます。
ピン留めされたショートカットは無効にできますが、コードから動的に削除することはできません。

Static Shortcuts
android:enabled属性をfalseにすることで無効になります。
無効時に表示されるメッセージはandroid:shortcutDisabledMessageで指定します。

Dynamic Shortcuts
ShortcutManagerクラスのdisableShortcutsメソッドを呼び出します。
引数にはdisableにするショートカットのIDを指定します。

shortcutManager.disableShortcuts(disableShortcutIds);

無効時に表示されるメッセージはShortcutInfo作成時にsetDisabledMessageメソッドで指定します。

ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, id)
                .setShortLabel(getString(R.string.short_label))
                .setIntent(intent)
                .setDisabledMessage(getString(R.string.disable_message))
                .build();

TIPSやはまったことなど

ショートカットが表示されない

以下の場合にショートカットが表示されませんでした。

  • android:shortcutIdが別のショートカットと重複している
  • android:shortcutIdにstringリソースを指定している
  • android:actionを指定していない

バックスタックに別のActivity積んだ上で遷移させたい

ActivityBに遷移させてバックキー押下時にAcitivtyAを表示させたいような場合は複数のIntentを指定します。
(ActivityA->ActivityBのように積まれている)

StaticShortcuts

<shortcut
    android:icon="@mipmap/ic_launcher"
    android:shortcutId="shortcut"
    android:shortcutShortLabel="@string/short_label">
    <!-- バックスタックに積みたいActivityの数だけintentを追加する 一番下に記述したintentが一番上に積まれる -->
    <intent
        android:action="android.intent.action.VIEW"
        android:targetPackage="com.kuwapp.myshortcutsample"
        android:targetClass="com.kuwapp.myshortcutsample.MainActivity" />
    <intent
        android:action="android.intent.action.VIEW"
        android:targetPackage="com.kuwapp.myshortcutsample"
        android:targetClass="com.kuwapp.myshortcutsample.SecondActivity" />
</shortcut>

DynamicShortcuts

ShortcutInfoのインスタンス生成時にsetIntentsメソッドの引数に複数のIntentを指定します。

Intent[] intents = // 略
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, id)
        .setShortLabel(getString(R.string.short_label))
        .setIntents(intents)
        .build();

StaticShortcutsを使用していてビルド時にパッケージ名を動的に変更しているときの対応

複数のAPKを共存させるためにビルド時にパッケージ名を変更している場合(debugとrelease版など)はStaticShortcutsのintent要素に追加するandroid:targetPackageも同時に変更しなければなりません。
アプローチとしては以下が考えられると思います。

  • パッケージ名の数だけショートカットの設定ファイルを作成しておく
    • BuildTypeやFlavorが増える毎にほぼ同内容の設定ファイルを追加しなければならない
  • ビルド時に動的に設定ファイルを書き換えるGradleのタスク等を作る
    • 一度作ってしまえば楽だが、少し手間がかかる
  • Static ShortcutsをやめDynamic Shortcutsを使用する
    • 単に置き換えるだけなので容易

Dynamic Shortcutsへの移行は容易なので自分はDynamicShortcutsを使用することにしました。

異なるショートカットから同一のActivityへ遷移させるが、ショートカット毎に振る舞いを変えたい

異なるショートカットから同一のActivityへ遷移させ表示内容を変えたいときなどはIntentのActionかExtraに値を入れておいてそれを見て処理を変えることで実現できます。
Static Shortcutsなら以下のようにandroid:actionに独自アクションを入れるかintent要素内にextraを記述して起動側で受け取るようにすればどのショートカットからきたか分かります。

<intent
    android:action="com.kuwapp.Intent.MAIN"
    android:targetClass="com.kuwapp.appshortcutssample.MainActivity"
    android:targetPackage="com.kuwapp.appshortcutssample">
    <extra android:name="Shortcut" android:value="Main" />
</intent>    

以下のようにAction or extraを取り出しどのショートカットからきたか判断して処理を変えれば良いです。

String action = getIntent().getAction();
String extra = getIntent().getStringExtra("Shortcut");

おわりに

AppShortcutsを実装してみました。現在Android7.1でしか使えませんが、実装は非常に容易であると同時に便利な機能なので入れてみても良いと思います。(いつかminSdkVersionが25になれば良いなあ・・・)
個人的にはDynamic Shortcutsの方が柔軟性があるのであえてStatic Shortcutsを使う意味があるのだろうかと思ったり。

参考

App Shortcuts | Android Developers