通知の有効無効状態が変化したことをアプリで受け取る方法

Androidではシステムの設定画面からアプリの通知の有効無効を変更できます。
Android 8以上であれば、通知チャンネルがありますので、すべての通知、通知チャンネルグループ単位、通知チャンネル単位、の3段階で有効無効を設定することができます。

|すべての通知|通知チャンネルグループ|通知チャンネル|
|--|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/151b4858-f700-c48b-4c67-61f7de26f341.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/30260af4-6fc0-c83f-da9f-4ab7b17fce16.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/448ef1bf-dbd0-42cb-4521-f4676a1cfb6a.png)|

ここで無効にされてしまうとアプリから通知を表示しようとしても表示されなくなってしまいます。
表示されないのに通知の処理をするのは無駄なので止めたいところですし、逆に有効に変更してもらったのなら通知処理を動かしたいですね。
アプリからはNotificationManagerから状態を取得することはできますが、ポーリングをするのもお行儀が悪いので、変化したトリガーを受け取って処理をしたいものです。

# 通知の有効無効状態の変化を通知するBroadcastIntent

この通知設定の有効状態が変化したことを通知するIntentがあります。有効から無効になった場合、無効から有効になった場合、どちらも以下のActionのBroadcastIntentが投げられます。

|設定|Action|
|--|--|
|すべての通知|"android.app.action.APP_BLOCK_STATE_CHANGED"|
|グループ|"android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"|
|チャンネル|"android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"|

要するに以下のようなintent-filterを設定しておくと受け取ることができます。

```xml:AndroidManifest.xml
<receiver
    android:name=".BlockStateChangedReceiver"
    android:enabled="true"
    android:exported="true"
    >
    <intent-filter>
        <action android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
        <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
        <action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
    </intent-filter>
</receiver>
```

これらのActionはNotificationManagerで定義されています。
`NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED`
`NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED`
`NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED`

また、有効無効の状態も"android.app.extra.BLOCKED_STATE"というkeyのBooleanExtraで格納されています。名前から分かると思いますが、無効になった場合にtrueが通知されます。こちらも`NotificationManager.EXTRA_BLOCKED_STATE`で定義されています。
また、通知グループや通知チャンネルの場合、グループIDは"android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID"、チャンネルIDは"android.app.extra.NOTIFICATION_CHANNEL_ID"というkeyで格納されています。これらも`NotificationManager.EXTRA_NOTIFICATION_CHANNEL_GROUP_ID`、`NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID`で定義されています。

それぞれ個別に検出するのであれば、以下のように判断します。

```kotlin
class BlockStateChangedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val blockedState = intent.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
        when (intent.action) {
            NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED -> {
                // すべての通知の有効無効が変化した
            }
            NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED -> {
                val groupId =  intent.getStringExtra(NotificationManager.EXTRA_NOTIFICATION_CHANNEL_GROUP_ID)
                // groupIdのチャンネルグループの有効無効が変化した
            }
            NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED -> {
                val channelId =  intent.getStringExtra(NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID)
                // channleIdのチャンネルの有効無効が変化した
            }
        }
    }
}
```

残念ながら、**これらの定義はAPI level 28なのでAndroid 9以上でのみ使える方法**と言うことになります。
NotificationManagerに定義されていないだけでIntentは飛んでいるんじゃなかろうかと淡い期待を抱いて調べましたが、そんなことはありませんでした。
通知チャンネルができたAPI level 26で追加しておいて欲しかった機能ですね。

また、通知されるのは有効無効の変化だけで、重要度の変更などでは投げられません。

以上です。

----

## 余談
チャンネルIDを格納するExtraのKeyの定義って複数ありますよね

|定義|値|
|--|--|
|NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID|"android.app.extra.NOTIFICATION_CHANNEL_ID"|
|Notification.EXTRA_CHANNEL_ID|"android.intent.extra.CHANNEL_ID"|
|Settings.EXTRA_CHANNEL_ID|"android.provider.extra.CHANNEL_ID"|

全部違う値なので注意しましょう。