概要
- Android Oreoから、プッシュ通知を行うためには**通知チャネル(Notification Channnel)**の利用が必須になっています。
- 通知チャネルにアプリオリジナルの通知音(アプリ内音源)を設定するための実装方法を書きました。
通知チャネルとは
通知チャネルは、Android 8.0(Oreo)から追加された新機能です。
通知チャネルを作成して管理する - Android Developers
プッシュ通知を種別ごとに複数のチャネルに割り振り、チャネルごとに、プッシュ通知におけるバイブレーションのオン・オフ、通知音の変更といった細かい設定をOSレベルで行える仕組みになっています。
例えば、LINEには以下のような通知チャネルが設けられています。
これにより、「通話やトークを受信したときの通知は受けたいけれども、グループに招待された際の通知は必要ない」といった場合にも、チャネルごとの設定を変更することで対応できます。
なお、一度作成したチャネルの設定については、アプリ側から変更できるのはチャネルの名前と説明のみで、通知音やバイブレーションなどの設定をアプリ側から任意に変更するには、対象の通知チャネルを削除したのちに再作成する必要があります。
(配信中のアプリにおいてこれを行うことは推奨されません。ただし、設定値を参照してユーザに対して設定の変更を促すといったことは可能です。)
通知チャネルにオリジナルの通知音を設定する
Android版LINEをお使いの方はご存知かと思いますが、LINEにはアプリオリジナルの通知音が複数存在し、デフォルトの設定ではその中の「シンプルベル」という名前の通知音が鳴動すると思います。
私が開発していたアプリにこれと同様の機能を実装する機会があったのですが、通知チャネルが必須となるAndroid O以降では少々複雑な実装になったため、それについて本記事にまとめました。
手順
1. 通知音の格納
res/raw
の配下に、通知音として使用する.wav
なり.mp3
なりの音源を入れておきます。
2. 通知チャネルを再作成
アップデート後の起動で、既存の通知チャネルを削除し、アプリオリジナルの音を設定した通知チャネルを再作成します。
3. 通知チャネルにアプリオリジナルのサウンドを設定する
通知チャネル作成と同時にNotificationChannel#setSound
を用いて、通知チャネルにアプリオリジナルのサウンドを設定します。
通知チャネルに音をセットするには、
protected open fun create(): NotificationChannel = create(NotificationManager.IMPORTANCE_HIGH).apply {
~
setSound(getOriginalSoundUri(R.original_sound), audioAttributes)
}
のようにして、NotificationChannel.setSound
の引数に、rawディレクトリに入れた音のURIと、audioAttributes
の2つを指定します。URIは以下のようにして取得しています。
fun getOriginalSoundUri(id: Int): String {
val sb = StringBuilder()
sb.append(ContentResolver.SCHEME_ANDROID_RESOURCE)
sb.append(File.pathSeparator + File.separator + File.separator)
sb.append(context.packageName)
sb.append(File.separator)
sb.append(context.resources.getResourceTypeName(id))
sb.append(File.separator)
sb.append(context.resources.getResourceEntryName(id))
return sb.toString()
}
ここでセットされた音をアプリの通知設定の画面から見ると、[アプリの通知音]という名前で設定されています。
4. オリジナル通知音を端末にコピー
ユーザが[アプリの通知音]を一旦変えてしまうと、アプリ側から再び[アプリの通知音]に戻すことは(通知チャネルを再作成しない限りは)できません。
ユーザ自身がオリジナルの通知音に設定し直せるようにするためには、
app/res/raw
ディレクトリの音を、
Android/Notifications
またはAndroid/media/[パッケージ名]/Notifications
に保存して、[アプリの通知設定]から参照できるようにする必要があります。
Android/Notifications
では、READ_EXTERNAL_STORAGE
のパーミッションが必要なうえ、アプリをアンインストールした後もファイルが残ってしまうことから、今回は
Android/media/[パッケージ名]/Notifications
に保存することにしました。
app/res/raw
ディレクトリに格納してある音は
context.resources.openRawResource(R.sound_name)
で参照できます。
まず、音源をAndroid/media/[パッケージ名]/Notifications
に保存するためのメソッド
exportOriginalSounds()
を定義します。
fun exportOriginalSounds() {
try {
val dir = File(context.externalMediaDirs[0], Environment.DIRECTORY_NOTIFICATIONS)
if (!dir.exists()) {
dir.mkdirs()
}
context.resources.openRawResource(R.original_sound).use { inputStream ->
val name = "OriginalSound.wav"
val path = File(dir.toString() + File.separator + name)
FileOutputStream(path, false).use { outputStream ->
inputStream.copyTo(outputStream)
}
// MediaScannerConnection
MediaScannerConnection.scanFile(
MangoContext.app,
arrayOf(path.toString()),
arrayOf(Constants.ORIGINAL_SOUND_MIME_TYPE)
) { scannedPath, scannedUri ->
val c: Class<*> = MediaScannerConnection::class.java
LogOut.d(c, "Scanned $scannedPath")
LogOut.d(c, "-> uri: $scannedUri")
}
}
} catch (e: IOException) {
LogOut.logException(e)
}
}
これを通知チャネル作成時に実行します。以下に例を記載します。
protected open fun create(): NotificationChannel = create(NotificationManager.IMPORTANCE_HIGH).apply {
~
setSound(DEFAULT_SOUND_URI, audioAttributes)
exportOriginalSounds()
}
これでユーザが音の設定からオリジナルの音も選択できるようになります。
ハマった点
exportOriginalSounds()
の中盤でMediaScannerConnection.scanFile()
を呼び出していますが、これはオリジナル通知音の音源をContentProviderに登録する必要があるためです。
詳しい説明はContextのリファレンスに記載されています。
If you supply a non-null type to this function, the returned file will be a path to a sub-directory of the given type. Though these files are not automatically scanned by the media scanner, you can explicitly add them to the media database with MediaScannerConnection.scanFile.
始めはMediaScannerConnection.scanFile()
を呼び出していなかったばかりに、端末を再起動するまでは通知音が設定画面から選択できない、という現象が発生していました。
まとめ
-
setSound
メソッドを用いることで、通知チャンネルにオリジナルの通知音を設定できる。 - オリジナルのリソースは、端末ストレージにコピーしないと再利用ができない。
- オリジナル通知音の音源は、恣意的にContentProviderに登録する必要がある。
- 公式ドキュメントは隅から隅まで漏らさず読むべし。