ちょっと前までは AppCompatActivity を使用した場合に PreferenceFragment が Support Library の Fragment を継承していないのでそのまま使えず歯がゆい思いをしていたが Android Support Library の Fragment を継承した PreferenceFragmentCompat が登場しその問題が解決された。 しかし古いアプリを移植しようとして RingtonePreference (通知音選択) がそのまま使えない事に気付いた。 SwitchPreferece には SwitchPreferenceCompat が用意されているのに RingtonePreference にはそういったものがない。

仕方が無いので普通の Preference で自分で実装することにした。

呼び出し側

暗黙的インテントを投げることで同機能を実装できる:

    mNotificationSound.setOnPreferenceClickListener {
        val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI)

        val existingValue = pref().getString("setting.notificationSound", null)

        // Uri として null を指定すると通知音「なし」扱いになることに注意
        val uri: Uri? = when {
            existingValue == null -> Settings.System.DEFAULT_NOTIFICATION_URI
            existingValue.length > 0 -> Uri.parse(existingValue)
            else -> null
        }
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri)
        startActivityForResult(intent, 0)
        true
    }

「Preference の設定がまだされていない場合」「通知音がなし (サイレント) の場合」「通知音が設定されている場合」の 3 通りあることに注意。 その場合分けは上のコードの when 文に示されている。

onActivityResult() 側

暗黙的インテントの戻り値を受け取る為に onActivityResult() も実装する:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode === 0 && data != null) {
        val ringtone = data.getParcelableExtra<Parcelable?>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
        pref().edit().putString("setting.notificationSound", ringtone?.let { it.toString() } ?: "").apply()
    } else {
        super.onActivityResult(requestCode, resultCode, data)
    }
}

こちらは通知音がなしの場合は null で渡ってくるので Preference に空文字で設定しておく。 空文字で無くても null でさえなければ何でも構わない。 null だと未設定との区別がつかなくなる。

ちなみに上記 val ringtone には content://media/internal/audio/media/22 といった感じの CONTENT_URI 表現が入ってくる。 実際に Notification を鳴らす際にこのまま使うことができる。