読者です 読者をやめる 読者になる 読者になる

なるようになるかも

力は多くの場合、その人の思いを超えない。

Androidのホームショートカット作成時の挙動

ICSの頃にLauncher2のソースを読んだときには、行儀良くショットカットを作るには以下のような記述が必要でした。

//ホームに作成したいショートカットをIntentとして作成
//(ここではActivityが自身のショートカットをホームに作成すると仮定)
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName(getPackageName(), getClass().getName());
shortcutIntent.setAction(Intent.ACTION_MAIN);
shortcutIntent.addCategory(Intent.CATEGORY_LAUNCHER);

//続いてショートカット作成のためのintentを生成
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "ショートカット名");
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
intent.putExtra("duplicate",false);
sendBroadcast(intent);

なお、ショートカットを作成するには、com.android.launcher.permission.INSTALL_SHORTCUTパーミッションをManifestに記述する必要があります。

ちょっとした解説

intent.putExtra("duplicate",false);は同じショートカットが生成されるのを防止するために必須でした。

キーの"duplicate"LauncherクラスにEXTRA_SHORTCUT_DUPLICATEとして定数定義されているのですが、パッケージプライベートのためにこのように指定するしかありません。指定しなかった場合のデフォルトはtrue(同一のショートカットの重複を許可する)です。

何をもって「同一のショートカット」と判定しているのかは、LauncherModelshortcutExists()を読めば分かります。Intent.EXTRA_SHORTCUT_NAMEで指定した名前とintentの内容をtoUri(0)で文字列化したものとを、比較しているようです。

Intent.ACTION_MAINIntent.CATEGORY_LAUNCHERの付与は、アプリがアンインストールされた際にショートカットも自動的に削除されるために必須でした。これを付与しないとアプリ削除後も無効なショートカットが残り続けます。

コンポーネント名だけでショートカット用のIntentを作成した場合には、Launcher2がIntent.ACTION_VIEWを付与します。しかしアプリケーション削除時に消されるのは、そのパッケージのIntent.ACTION_MAINIntent.CATEGORY_LAUNCHERのショートカットだけなのです。

Launcher2自体にはパッケージの削除を関知するブロードキャストレシーバが存在しなかったため、具体的にこの処理がどこで行われているのか追跡していないのですが、アプリがアンインストールされると、システムがcom.android.launcher.action.UNINSTALL_SHORTCUTを投げてショートカットの削除を行っているのだと推測しています。

UninstallShortcutReceiverはショートカットの同一性をIntentfilterEquals()で判定しており、ActionとCategoryを一致させる必要があるのです。

KitKatでの挙動の変化

Intent.EXTRA_SHORTCUT_NAMEの指定が任意になりました。未指定の場合、PackageManagerから自動的にアプリ名を取得して設定します。

Intent.ACTION_MAINIntent.CATEGORY_LAUNCHERも指定する必要がなくなりました。アプリがアンインストールされると、そのアプリのパッケージのショートカットは全て消えます。

ただし判定はコンポーネント名で行われていると思われるので、ウェブサイトをACTION_VIEWで表示するようなものについては残ります。

また、Intent.ACTION_MAINIntent.CATEGORY_LAUNCHERが明示的に指定されている場合、Intent.FLAG_ACTIVITY_NEW_TASKIntent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDEDの起動フラグが設定されるようになりました。

duplicateについても、指定する必要がなくなりました。Androidソースコード上では、相変わらず指定されない場合、デフォルト値としてtrueが与えられるはずなのですが、ショートカットが多重に生成されることはありません。

//ホームに作成したいショートカットをIntentとして作成
//(ここではActivityが自身のショートカットをホームに作成すると仮定)
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName(getPackageName(), getClass().getName());

//続いてショートカット作成のためのintentを生成
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
sendBroadcast(intent);

よって、これだけの記述でよくなりました。(下位互換を考えるとIntent.EXTRA_SHORTCUT_NAMEを指定する必要はあります)

余談

手元のNexus5だと、 duplicatetrueにして複数のショートカット生成しようとすると、NullPointerExceptionで落ちます

そもそもduplicatetrueにしてもショートカットを重複させることができなくなっています。しかしソースコード上では相変わらずこの値を見ているので、何がどうバグってるのか謎。