Android 8.0 Oreo 通知対応チェックリスト


Android 8.0 Oreoがリリースされ、Lollipop以来の大きな通知関係の変更が入りました。
[Lollipopのときにも同人誌にまとめたり](https://qiita.com/mstssk/items/099a22976dd2dccdd1ea)しましたが、今回もTechBoosterのEdge of Androidという本に「ツは通知のツ」というタイトルでOreoでの通知の変更点をまとめました。
Edge of Androidは https://booth.pm/ja/items/586710 から購入できます。

本記事は「ツは通知のツ」から特に実装対応が必要な点について抜き出した簡易まとめです。
「ツは通知のツ」の中ではその他の挙動の変更などについてもまとめています。

※ただし、Android O Developer Preview 3の頃に動作確認を行った内容を元にしているので、もしかしたら変わっているかもしれません。
当時の動作確認時に作成したサンプルアプリケーションは https://github.com/mstssk/O_Notification で公開しています。

## 通知チャンネルを使う

targetSdkVersionをOreo(26)以降にすると、Oreoで追加された **通知チャンネル** を使っていないと、通知が表示されなくなってしまいます。

しかも、2018年8月以降はGoogle Playにアプリを公開するにはtargetSdkVersionを26以上にすることが必須になるとアナウンスされており、通知チャンネルへの対応はAndroidアプリ開発者として必須です。
https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html


### 通知チャンネルの登録と使用

通知チャンネルは次のように登録します。
これはサポートライブラリでCompatが提供されていないので `@RequiresApi(api = Build.VERSION_CODES.O)` や `if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ ... }` などでコンパイルエラーを回避しましょう。

```java
String channelId = "updates"; // 通知チャンネルのIDにする任意の文字列
String name = "更新情報"; // 通知チャンネル名
int importance = NotificationManager.IMPORTANCE_HIGH; // デフォルトの重要度
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
channel.setDescription("通知チャンネルの説明"); // 必須ではない

// 通知チャンネルの設定のデフォルト値。設定必須ではなく、ユーザーが変更可能。
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
channel.enableVibration(true);
channel.enableLights(true);
channel.setLightColor(argb);
channel.setSound(uri, audioAttributes);
channel.setShowBadge(false); // ランチャー上でアイコンバッジを表示するかどうか

// NotificationManagerCompatにcreateNotificationChannel()は無い。
NotificationManager nm = getSystemService(NotificationManager.class);
nm.createNotificationChannel(channel);
```

上記のように通知チャンネルを登録した後で、通知に通知チャンネルのIDを設定してあげます。

```java
// 通知チャンネルのIDを指定するコンストラクタが増えている
NotificationCompat.Builder builder
  = new NotificationCompat.Builder(context, channelId)
    .setContentTitle("タイトル")
    .setContentText("テキスト")
    .setSmallIcon(R.drawable.ic_notifications);
NotificationManagerCompat.from(context).notify(id, builder.build());
```

### 通知チャンネルの削除

アプリの設計の変更やユーザーのログアウトなどにより通知チャンネルが不要になったら、アプリで削除する実装を行う必要があります。

```java
String channelId = "old_channel"; // 削除する通知チャンネルのID
NotificationManager nm = getSystemService(NotificationManager.class);
nm.deleteNotificationChannel(channelId);
```

### 通知チャンネルのグループ化

通知チャンネルはグループ化できます。
グループ化しておくとOS側の通知設定画面で見やすくなります。

```java
NotificationManager nm = getSystemService(NotificationManager.class);

String groupId = "group_timeline"; // 通知チャンネルグループのID
String name = "タイムライン";        // グループ名
NotificationChannelGroup group = new NotificationChannelGroup(groupId, name);
nm.createNotificationChannelGroup(group);
// 削除する時はdeleteNotificationChannelGroup()を使う

NotificationChannel channel = ...;
channel.setGroup(group.getId());   // 通知チャンネルにグループIDを指定する
nm.createNotificationChannel(channel);

```

### 通知チャンネルの使い方

通知チャンネルのAPIは動的に通知チャンネルを登録したり削除したりすることを前提にしたものになっています。
Google公式の開発ドキュメントでは、マルチユーザーなアプリの場合にログインしているユーザー毎に通知チャンネルグループを作ったりする例が上げられています。
別の例では、AndroidのChromeブラウザはWeb Notificationを有効にしたサイトごとに通知チャンネルを作るようになっています。

通知をあまり多用しないアプリの場合は、Applicationクラスの中で起動時に常に通知チャンネルを登録するといったアプローチで作っている方もいるようです。

## Major Ongoing通知の背景色

Oreoで通知の種類が整理されました。Major Ongoing、People to People、General、BTW(その他)の4種類です。
その中で、Foregroundサービスと紐付いて表示される通知がMajor Ongoingのものです。

Major Ongoingの通知は背景色を指定できるという機能を持っています。

```java
@ColorInt int color = ...;
NotificationCompat.Builder builder
  = new NotificationCompat.Builder(this, channelId)
    .setContentTitle("通知タイトル")
    .setContentText("通知テキスト")
    .setSmallIcon(R.drawable.icon)
    .setColor(color)
    .setColorized(true); // colorを通知の背景色にする
startForeground(id, builder.build());
```

## アイコンバッジ

ランチャー上のアプリアイコンにバッジを表示できるようになりました。

通知チャンネルの設定で有効になっていると表示されます。アプリ側からは `NotificationChannel#setShowBadge()` にtrueを設定できます。更に `Notification#setNumber()` で数字を設定しているとバッジに表示されます。

ただし、対応しているランチャーアプリでないと表示されず、現状動作確認できる実機は少ないです。

## 通知の設定画面へのショートカット

LollipopのころからOS側の設定画面とリンクできる仕組みはありましたが、Oreoで強化されています。

### OS側からアプリ側

通知の設定画面のIntentカテゴリである[INTENT_CATEGORY_NOTIFICATION_PREFERENCES](https://developer.android.com/reference/android/app/Notification.html#INTENT_CATEGORY_NOTIFICATION_PREFERENCES)を設定しているアクティビティが呼ばれる際に、どの通知チャンネルの設定画面からの呼び出しなのかが分かるようになりました。

```java
// 通知チャンネルに対応するFragmentを表示する例
if (getIntent().hasCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)) {
  String channelId = getIntent().getStringExtra(Notification.EXTRA_CHANNEL_ID);
  int position = getFragmentPositionByChannelId(channelId);
  mViewPager.setCurrentItem(position);
}
```

### アプリ側からOS側

OSの設定画面を開くIntentが追加されました。このアクションは通知の設定画面のトップを開く場合と、通知チャンネルの設定画面を開く場合の2つがあります。

```java
// OS側の通知設定画面のトップを開く実装
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(intent);
```

OS側の通知の設定画面を開く機能は以前から実装できましたが、Android Oでは改めて公開APIとして整備されました。
https://stackoverflow.com/questions/32366649/any-way-to-link-to-the-android-notification-settings-for-my-app,https://stackoverflow.com/questions/32366649/


```java
// 通知チャンネルの設定画面を開く実装
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(intent);
```

通知チャンネルの登場により、今までアプリ側で実装する必要があった通知の設定はOS側で行われます。
ユーザを適切に設定画面に誘導するよう心がけましょう。

## タイムアウト機能

Oreoでは任意のミリ秒後に消える通知を作れます。

```java
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
    .setContentTitle("3秒で消える通知")
    .setContentText("この通知は3秒で消えます。")
    .setSmallIcon(R.drawable.small_icon)
    .setTimeoutAfter(3000); // タイムアウトの秒数をミリ秒で指定する
NotificationManagerCompat.from(context).notify(1, builder.build());
```

## さいごに

本記事ではあくまで実装まわりの変更点のみフォーカスしています。
その他にも挙動の変更がいくつかあり、記事冒頭で紹介した本ではそちらも紹介しているので、お手にとっていただければ幸いです。