Help us understand the problem. What is going on with this article?

Androidの通知チャネルにオリジナル通知音を設定する

概要

  • Android Oreoから、プッシュ通知を行うためには通知チャネル(Notification Channnel)の利用が必須になっています。
  • 通知チャネルにアプリオリジナルの通知音を設定するための手順と、その際にハマった点について書きます。

通知チャネルとは

通知チャネルは、Android 8.0(Oreo)から追加された新機能です。

通知チャネルを作成して管理する - Android Developers

プッシュ通知を種別ごとに複数のチャネルに割り振り、チャネルごとに、プッシュ通知におけるバイブレーションのオン・オフ、通知音の変更といった細かい設定をOSレベルで行える仕組みになっています。

例えば、LINEには以下のような通知チャネルが設けられています。

これにより、「通話やトークを受信したときの通知は受けたいけれども、グループに招待された際の通知は必要ない」といった場合にも、チャネルごとの設定を変更することで対応できます。

なお、一度作成したチャネルの設定については、アプリ側から変更できるのはチャネルの名前と説明のみで、通知音やバイブレーションなどの設定をアプリ側から任意に変更するには、対象の通知チャネルを削除したのちに再作成する必要があります。

通知チャネルにオリジナルの通知音を設定する

自社開発のチャットアプリにオリジナルの通知音を設定する案件があり、通知チャネルが必須となるAndroid O以降へ対応をした際にハマってしまった場面があったため、記事にしました。

手順

1. 通知音の格納

スクリーンショット 2020-01-06 11.03.24.png
ここの配下に、通知音として使用する.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)
             }
          }
      } catch (e: IOException) {
          LogOut.logException(e)
      }
}

この関数を通知チャネル作成時に実行します。

protected open fun create(): NotificationChannel = create(NotificationManager.IMPORTANCE_HIGH).apply {

        ~

        setSound(DEFAULT_SOUND_URI, audioAttributes)
        exportOriginalSounds()
}

これでユーザが音の設定からオリジナルの音も選択できるようになるはずでした。

以上の手順でハマった点

以上の手順を踏めば、[アプリの設定>[任意の通知チャネル]>詳細設定>音]で表示されるリストに、コピーしたオリジナルの音源が表示されるようになっているはずでしたが、なぜか表示されず...
ファイルが正常に保存されていないのかもと思い、ファイルマネージャからAndroid/media/[パッケージ名]/Notificationsを確認しましたが、正常に音源が保存されていました。
試行錯誤していく中で、端末を再起動すれば音源が表示されることが分かりました。

解決方法

「端末を再起動すれば表示される」ということは、
端末の起動時にAndroid/media/[パッケージ名]/Notificationsに通知音としてのインデックスを貼るための、何らかの処理がなされているはずです。
つまり、手順を踏んだ後に強制的にインデックスを貼る処理を行えば良さそうです。

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によって、
オリジナル通知音の音源をContentProviderに登録する必要があるようです。

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.scanFileを追記
             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)
      }
}

まとめ

  • setSoundメソッドを用いることで、通知チャンネルにオリジナルの通知音を設定できる。
  • オリジナルのリソースは、端末ストレージにコピーしないと再利用ができない。
  • オリジナル通知音の音源は、恣意的にContentProviderに登録する必要がある。
  • 公式ドキュメントは隅から隅まで漏らさず読むべし。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした