AndroidのNotificationが削除されたことを検知する

#はじめに
「Androidの通知をタップするとActivityが起動する」というのはよく見かけますが、その逆の「通知が消されたのを検知して何らかのアクションをする」というのはあまり見かけないと思ったので調べてみました。

どうやらNotificationCompat#setDeleteIntent()というメソッドとBroadcastReceiverを使えば実現出来そうだということが分かったのでまとめます。

今回はサンプルとして、ボタンを押すと通知が表示され

 - 通知をタップすると「通知がタップされました」というToastが表示される
 - 通知をスワイプして削除(若しくは全部消す)すると「通知が削除されました」というToastが表示される 

という簡易的なアプリを用いて説明します

#実装

実装の大まかな流れとしては

1. 通知を組み立てるときにNotificationCompat#setDeleteIntent()を呼び、通知削除時の挙動を設定する。
2. 削除時の挙動にPendingIntent#getBroadcast()を指定し、BroadcastReceiverが受け取れるようactionを設定する。
3. 削除時に投げられたPendingIntentをBroadcastReceiverで受け取り、actionに応じた処理を行う。

となります。

下記が主要部分のソースです。


```java:MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.buttonBroadcast).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showNotificationWithBroadcast();
            }
        });

    private void showNotificationWithBroadcast() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        Notification notification = builder
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Notification Title")
                .setContentText("Notification Text")
                .setContentIntent( //通知タップ時のPendingIntent
                        getPendingIntentWithBroadcast(NotificationReceiver.CLICK_NOTIFICATION)
                )
                .setDeleteIntent(  //通知の削除時のPendingIntent
                        getPendingIntentWithBroadcast(NotificationReceiver.DELETE_NOTIFICATION)
                )
                .build();

        ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).notify(0, notification);
    }

    private PendingIntent getPendingIntentWithBroadcast(String action) {
        return PendingIntent.getBroadcast(getApplicationContext(), 0 , new Intent(action), 0);
    }
```

このサンプルではactionをswitch文で分岐させていますが、拾いたいのが削除イベントだけでいい場合は下記のように書いてもいいと思います。

```java
String action = intent.getAction();
if (action.equals(DELETE_NOTIFICATION)) {
    //do something
}

```
また後述もしていますが、AndroidManifest.xmlへの定義の追加も忘れないように行いましょう。

```java:NotificationReceiver.java
public class NotificationReceiver extends BroadcastReceiver{

    //MainActivity側からも参照されるのでpublic
    public static final String CLICK_NOTIFICATION = "click_notification";
    public static final String DELETE_NOTIFICATION = "delete_notification";

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        switch (action) {
            case CLICK_NOTIFICATION:
                //通知タップ時のイベントを書く
                Toast.makeText(context, "通知がタップされました", Toast.LENGTH_SHORT).show();
                break;

            case DELETE_NOTIFICATION:
                //通知削除時のイベントを書く
                Toast.makeText(context, "通知が削除されました", Toast.LENGTH_SHORT).show();
                break;

            default:
                break;
        }
    }
}
```

receiverを追記し、intent-filterに定義したactionを定義しておかないと引っ掛けてくれません。

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

    <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>
        </activity>
        <receiver android:name=".NotificationReceiver">
            <intent-filter>
                <!--定義したactionを追記する-->
                <action android:name="click_notification" />
                <action android:name="delete_notification" />
            </intent-filter>
        </receiver>
    </application>

</manifest>
```
#動かしてみる
ちょっと分かり辛いかもしれませんが、通知をタップした時、通知をスワイプして消した時でそれぞれToastが表示されています。
![NotificationDeleteIntentSample-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/68671/29dba0f3-b914-d7d8-42fb-0518f88e0195.gif)

#おまけ

Broadcastを用いずに、よくある通知タップ時のようにActivityの起動やほかアプリの呼び出しにすることも可能です。
下記は、通知をタップするとYahoo!JAPANが、通知を削除するとQiitaがそれぞれブラウザで立ち上がるような通知です。
サンプルアプリに更に実装を追加しています。

※削除した時にもブラウザが立ち上がるという仕様はいかがなものかと思います。

```java:MainActivity.java
    private void showNotificationWithIntent() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        Notification notification = builder
                .setWhen(System.currentTimeMillis())
                .setContentTitle("Notification Title")
                .setContentText("Notification Text")
                .setContentIntent( //通知タップ時のPendingIntent タップされたらYahoo!をブラウザで表示
                        getPendingIntent("http://www.yahoo.co.jp")
                )
                .setDeleteIntent(  //通知の削除時のPendingIntent 削除されたらQiitaをブラウザで表示
                        getPendingIntent("http://qiita.com")
                )
                .setAutoCancel(true)
                .setSmallIcon(R.mipmap.ic_launcher)
                .build();

        ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).notify(0, notification);
    }

    private PendingIntent getPendingIntent(String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
    }
```
![NotificationDeleteIntentSample.gif](https://qiita-image-store.s3.amazonaws.com/0/68671/d8fa8654-f058-5f26-7101-5e9731b0dc9a.gif)


#おわりに
通知の削除をトリガーにして何かをしているアプリをあまり知りませんが、そもそも通知を削除しているにも関わらず、前面に出てくるのはおかしいと思いました。~~じぶん銀行アプリ、お前のことだよ。~~
逆に、「通知が消された」というログを裏側で送信するなどユーザに認知されない部分で出来る実装の可能性は感じました。

今回のソースは下記です。
https://github.com/masaibar/NotificationDeleteIntentSample

#参考
[android - How to use Notification.deleteIntent - Stack Overflow](http://stackoverflow.com/questions/7465849/how-to-use-notification-deleteintent)