複数の不要なAlarmをまとめてCancelする方法を考える（Android）

目覚まし時計アプリだったり、定期的なプッシュ通知を出すなどに便利なAlarm。
しかし、複数のアラームをセットした時がなかなか曲者です。
不要なアラームはまとめて削除したい。その方法を考えます。

結論としては、**requestCodeを変えて登録しておくと良い**、と言った感じです（詳しくは後半）。

# 前提

複数登録するアラームは、同じBroadcastReceiverを呼びます。

```java
Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);

// AlarmReceiver: アラーム起動で呼ばれるクラス、BroadcastReceiverをextendsしてある
```

# 基本的な登録と削除の方法 （1つの場合）

基本的には、登録したIntentと同じIntentを使用してキャンセルします。
コードを見るのが早いと思うので、実装を以下に。

```java:登録
// アラームの登録
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);

alarmManager.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY * 7, pendingIntent);

// calendar: Calendarのインスタンス、アラームが起動する時刻を設定してある
// AlarmReceiver: アラーム起動で呼ばれるクラス、BroadcastReceiverをextendsしてある
```

```java:削除
// アラームの削除
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);

pendingIntent.cancel();
alarmManager.cancel(pendingIntent);
```

見ての通り、登録・削除で違いがあるのは、AlarmManagerにsetしているかcancelしているか程度です。
alarmManagerだけキャンセルしてもできるようですが、pendingIntentもcancelしておくほうが良さそうです。。。

# 複数のAlarmを登録・削除する場合

今回の場合は、特に**「Receiverが同じ」**場合です。ここでは、複数のパターンを考えます

## Intent.setTypeを使うパターン

intent.setType()を使用して、別のintentであることを明確にするパターンです。
まずは、実装例を見てみます。

### 登録処理
```java:登録
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

Intent intent1 = new Intent(getApplicationContext(), AlarmReceiver.class);
intent1.setType("first alarm");    // このsetTypeが重要
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(getApplicationContext(), 0, intent1, 0);
alarmManager.setRepeating(AlarmManager.RTC, calendar1.getTimeInMillis(), AlarmManager.INTERVAL_DAY * 7, pendingIntent1);

Intent intent2 = new Intent(getApplicationContext(), AlarmReceiver.class);
intent2.setType("second alarm");    // このsetTypeが重要
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(getApplicationContext(), 0, intent2, 0);
alarmManager.setRepeating(AlarmManager.RTC, calendar2.getTimeInMillis(), AlarmManager.INTERVAL_DAY * 7, pendingIntent2);
```

上のコードでは`setType`でintentを区別するようにしました。
実際に、以下のコードでintentが区別されていることを確認することができます。

```java
intent1.filterEquals(intent2);    // returns false.
```

#### setTypeしなかった場合

```java
intent1.filterEquals(intent2);     // returns true.

intent1.putExtra(..., ...);
intent1.filterEquals(intent2);     // returns true.
```

**intent.putExtraとかしても、trueになる = 同じintentとして扱われる**のでご注意。setTypeが重要です。

### 削除処理

例によって、**同じインテントを作って、削除する**ので、ここでもsetTypeが重要です。
先にセットした２つのアラームのうち、2つめを削除する例を挙げてみます。

```java:削除処理
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
intent.setType("second alarm");    // このsetTypeが重要

PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);

pendingIntent.cancel();
alarmManager.cancel(pendingIntent);
```

これで2つめのアラームだけをキャンセルすることができました！

### setTypeの問題点

setTypeで、intentを区別することができる事がわかりました。しかし、問題点もあります。

#### すべてキャンセルしたい時の処理が地味に面倒

仮に、アプリのアップデート時に過去のアラームをすべて削除する場合、**過去に使用したアラームのタイプをすべてチェックして**、削除する必要があります。
すべてのユーザが、直前のバージョンからアップデートするわけではありませんので、過去のタイプを、持ち続けなければならないんですね。。。

（むしろアプリを再インストールしてもらいたい）

## requestCodeを分けるパターン

上では、setTypeでintentを区別する方法を上げましたが、キャンセル処理が面倒だということがわかりました。もっとすっきり考えられる方法はないのか。
ここではrequestCodeを使った方法を考えます。

### 登録と削除を一気に処理

基本的には、requestCodeをincrementしていく運用を考えます。
そうすれば、必要なrequestCodeより小さいものは全てキャンセルする処理が可能になります。

```java:登録
int ACTIVE_REQUEST_CODE = 12;    // たとえば、requestCodeが12以上のアラームを有効にしておくとして

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

// 不要になった過去のアラームをまとめて削除する
// requestCodeを0から登録していたとする
for (int requestCode = 0; requestCode < ACTIVE_REQUEST_CODE; requestCode++) {
    Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), requestCode, intent, 0);

    pendingIntent.cancel();
    alarmManager.cancel(pendingIntent);
}

// 新しいアラームの登録、必要なアラームの再登録
Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), ACTIVE_REQUEST_CODE, intent, 0);
alarmManager.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY * 7, pendingIntent);
```

この実装にしておけば、アップデートで一気にアラーム周りを刷新する場合においても、簡潔なコードで書けるのではないかなと予想しております。

# 結論

以上のような検討から、やはり、
アラームを複数登録する場合は、その後の運用も考えて、**requestCodeをincrementして登録していく**のが良さそう！！