Androidでミュートの解除/設定をする

# はじめに
この記事は[Android Advent Calendar 2022](https://qiita.com/advent-calendar/2022/android) 24日目の記事です。

やりたかったことはAndroidで強制的に音を鳴らすことだったのですが、ミュート解除周りでハマったのでその時の話を書きます。

# コードからミュートを解除する
## adjustStreamVolumeを使う
コードからミュートを解除するには[AudioManager](https://developer.android.com/reference/kotlin/android/media/AudioManager)のメソッドである`adjustStreamVolume`を使うとできます。このメソッドは音量を調節するためのメソッドですが、ミュートとアンミュートを指定することができます。
第一引数には使用したいストリームを指定します。今回はアラーム音のためのストリームである`STREAM_ALARM`を使用します。第2引数には音量を上げるか下げるかを指定することができます。この第2引数に`ADJUST_UNMUTE`を指定すると、ミュートを解除することができます。
第3引数は flag です。今回は0で大丈夫です。

この辺りの定数に関しては[こちら](https://developer.android.com/reference/kotlin/android/media/AudioManager#adjustStreamVolume(kotlin.Int,%20kotlin.Int,%20kotlin.Int):~:text=audio%20mode%20changes-,Constants,-static%20String)をご覧ください。

```kotlin
val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager

try {
    audioManager.adjustStreamVolume(
        AudioManager.STREAM_ALARM,
        AudioManager.ADJUST_UNMUTE,
        0
    )
    // 音量を5に設定
    audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0)
} catch (e: Exception) {
Timber.e("ミュートの解除に失敗しました。", e)
}
```

しかし、実はこれだと端末によっては通知オフ状態(サイレントモード)は解除できません。バイブレーションモードのみ解除することができます。

## ringerModeを変更する
別の方法として、`ringerMode`を変更する方法があります。
`AudioManager`の`ringerMode`を`RINGER_MODE_NORMAL`に変更します。

```kotlin
val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager

try {
    audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL

    // 音量を5に設定
    audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0)
} catch (e: Exception) {
Timber.e("ミュートの解除に失敗しました。", e)
}

```

# コードからサイレントモードを設定する
ミュートを解除して必要な効果音等を鳴らした後、もう一度サイレントモードやバイブレーションモードに戻したいですよね。

サイレントモードやバイブレーションモードに戻すにはまずAndroidManifestに`ACCESS_NOTIFICATION_POLICY`のパーミッションを追加します。
```
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
```

その上で`ringerMode`を元に戻します。そのために解除する時に元の状態を記憶しておく必要があります。

```kotlin
val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager

try {
    // 元の状態を記憶
    isVibration = audioManager.ringerMode == AudioManager.RINGER_MODE_VIBRATE
    isSilent = audioManager.ringerMode == AudioManager.RINGER_MODE_SILENT
    // サイレントモードを解除
    audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL

    // 音量を5に設定
    audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0)
} catch (e: Exception) {
Timber.e("ミュートの解除に失敗しました。", e)
}

// 元の状態に戻す
if (audioManager.ringerMode == AudioManager.RINGER_MODE_NORMAL) {
    // バイブレーションに戻す
    if (isVibration) {
        audioManager.ringerMode = AudioManager.RINGER_MODE_VIBRATE
    } else if (isSilent) {
    // サイレントモードに戻す
    audioManager.ringerMode = AudioManager.RINGER_MODE_SILENT
    }
}
```
しかし、ここで問題があります。

## もう一度サイレントモードを解除…できない？
コードからサイレントモードに設定した後、もう一度サイレントモードを解除することができなくなります。
これはAndroidのサイレントモードの仕様が原因です。


# Androidのサイレントモードの仕様
Androidには、音量から変更できるいわゆる通知オフの状態と、設定画面などから設定することができる強いサイレントモードの2種類があります。
コードからサイレントモードの設定をすると、この強いサイレントモードに移行してしまいます。
そして、この強いサイレントモードは、端末によってあったりなかったりします。設定の方法もバラバラです。
社内のいくつかの検証端末で確認した結果、以下のような結果になりました。

| 機種名 | OSバージョン | 強いサイレントモードの有無 | 設定場所 |
| ----- | :-----: | :-----: | ----- |
| Pixcel 6 Pro | 11 | あり | コントロールセンター |
| Xperia 10 Ⅱ / Ⅲ | 11 | あり | 設定画面 |
| OPPO A55s 5G | 11 | あり | 設定画面 |
| Galaxy Z Flip3 5G | 12 | なし | |
| HUAWEI nova lite 3 | 9 | なし　| |
| AQUOS R2 compact | 9 | なし | |

そして、**強いサイレントモードに移行してしまうとコードからは解除できなくなってしまいます。**

私が確認した中で強いサイレントモードにしか移行できない端末は以下の2つでした。この端末は通知オフを解除/設定することもできませんでした。
- Pixcel 6 Pro
- Xperia 10 Ⅱ / Ⅲ

通知オフの状態を解除/設定できる端末は以下の端末でした。
- OPPO A55s 5G
- Galaxy Z Flip3 5G

この2つに関しては、[adjustStreamVolumeを使う](##adjustStreamVolumeを使う)方法で通知オフ状態の解除ができました。しかしGalaxyに関しては、通知オフ/バイブレーション状態でもringerModeがNORMALから変更されませんでした。その状態でrigerModeを変更しようとすると、アプリがクラッシュしてしまいました。

以下の端末は通知オフの状態の解除/設定はできませんでした。
- HUAWEI nova lite 3
- AQUOS R2 compact

# まとめ
コードをからミュートを解除する方法は[adjustStreamVolumeを使う](##adjustStreamVolumeを使う)方法と[ringerModeを変更する](##ringerModeを変更する)方法があります。しかし、サイレントモードには機種によって強いサイレントモードに移行してしまうものがあり、元の状態に戻せなくなってしまうことがありました。
結局私は[adjustStreamVolumeを使う](##adjustStreamVolumeを使う)を採用し、音が鳴らない機種があることに関しては認めて貰うことになりました。。アプリ側から強いサイレントモードを設定してしまうのも、ミュート状態を元に戻さないのも理想的ではないので…
この辺り知見ある方がいらっしゃいましたら教えてくださるとありがたいです。

## 参考
- [AudioManager](https://developer.android.com/reference/kotlin/android/media/AudioManager)
- [音を制御する - AudioManager](https://qiita.com/KeithYokoma/items/3896f5934478fa560a50)
- [[Android] マナーモード状態かを取得する](http://stren-blog.seesaa.net/article/394884883.html)
- [【Android】AudioManagerのgetRingerModeで取得できるRINGER_MODE_SILENTについて](https://hiiro-game.seesaa.net/article/475546500.html)