問題
Android OS等から Activity に対して投げられる Intent がある。
例えば Suica 等 IC カードの内容は NFC を用いて読むことができるが、その場合 Manifest ファイルで Activity に IntentFilter を記述すると、カードをスマホにかざした時 Activity に対し Intent が投げられる。(参考:NFC basics)
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<!-- Launcher からの起動 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- NFC card からの起動 -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_filter" />
</activity>
ユーザが一々アプリを立ち上げてからカードをかざさなくても、カードをかざせば自動的にアプリが立ち上がるので使い勝手が良い。
ところがこれだと、スマホと IC カードを同じケースに入れたりして近い場所に置くと、アプリを起動するつもりが無くても自動的に起動してしまう。
これは場合によっては非常に煩わしい。
ICカードをスマホにかざした時に自動的にアプリが立ち上がるかどうかはユーザーが設定できることが望ましい。
この様に Activity が Intent を受け取るかどうかを動的に変更できると都合が良い場合がある。
しかし Manifest に静的に記述した IntentFilter を直接 on/off する方法は検索した限りでは見つからなかった。
解決法
色々と検索した結果、activity-alias を用いれば間接的にこれを実現できることが分かった。
参考にしたのは stack overflow の Handle NFC intents only when preference is set のページ。
ここを読めば良いが日本語でこの方法を説明しているページは見つからなかったので以下にまとめておく。
最初は Receiver で Intent を受けるようにして、動的にレシーバーを登録すれば良いのではと思ったが、試してみても Intent を受け取ることは出来なかった。
考えてみれば Intent を何に対して投げるかは投げるほうが決めているので、Receiver で受けようとしても受かるはずがない。Activity に投げる時は startActivity 等を使っているはずなので、これを Receiver で受けることはできない。
そうするとやはり Manifest に静的に記述した IntentFilter を on/off する方法が必要になる。
そのためにはまず Manifest ファイルに activity-alias を用意し、こちらに IntentFilter の記述を移す。
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<!-- Launcher からの起動 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
android:name=".MainActivityAlias"
android:enabled="true"
android:targetActivity=".MainActivity">
<!-- NFC card からの起動 -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_filter" />
</activity-alias>
activity-alias の android:name 属性に記述した MainActivityAlias に対応する Java のクラスは必要ない。
また android:targetActivity には元々の Activity のクラス名を記述する。
android:enabled にはアプリインストール時、デフォルトでこの IntentFilter を有効にするかどうかを記述する。上記の場合はアプリインストール時はこの IntentFilter が有効になっている。
そしてこれをアプリから変更する場合は
// カードをかざした時に自動で起動するかどうかを設定する
String pacageName = getContext().getPackageName();
int newState = startByCard ?
PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
getContext().getPackageManager().setComponentEnabledSetting(
new ComponentName(pacageName, pacageName + ".MainActivityAlias"),
newState, PackageManager.DONT_KILL_APP);
等とする。
startByCard はカードをかざした時に Activity で Intent を受けるかどうかを示すフラグ。
受けるときには newState に PackageManager.COMPONENT_ENABLED_STATE_ENABLED をセットする。
受けない時は PackageManager.COMPONENT_ENABLED_STATE_DISABLED をセットする。
そして PackageManager#setComponentEnabledSetting を呼び出し MainActivityAlias コンポーネントの enabled 属性を設定する。
そうすると Manifest の MainActivityAlias(activity-alias) に記述したもののみ(この場合は NFC の IntentFilter)が有効または無効にされる。MainActivity 自体は有効なままとなる。
確認はしていないが Activity に限らず Service にも適応できるはず。また IntentFilter だけでなく他の Manifest で静的に記述するプロパティもこの方法で動的に変更できるようになると思われる。