7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Android Advent Calendar 2019

Day 13

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

Last updated at Posted at 2020-01-06

概要

  • 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に登録する必要がある。
  • 公式ドキュメントは隅から隅まで漏らさず読むべし。
7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?