1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.settings.notification
18
19 import android.app.INotificationManager
20 import android.app.NotificationManager
21 import android.app.NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED
22 import android.content.BroadcastReceiver
23 import android.content.Context
24 import android.content.Context.NOTIFICATION_SERVICE
25 import android.content.Intent
26 import android.content.IntentFilter
27 import android.media.AudioManager
28 import android.media.AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION
29 import android.media.AudioManager.RINGER_MODE_NORMAL
30 import android.media.AudioManager.RINGER_MODE_SILENT
31 import android.media.AudioManager.RINGER_MODE_VIBRATE
32 import android.media.AudioManager.STREAM_RING
33 import android.os.ServiceManager
34 import android.os.UserManager
35 import android.os.Vibrator
36 import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS
37 import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS
38 import androidx.preference.Preference
39 import com.android.settings.PreferenceRestrictionMixin
40 import com.android.settings.R
41 import com.android.settingslib.datastore.KeyValueStore
42 import com.android.settingslib.datastore.NoOpKeyedObservable
43 import com.android.settingslib.metadata.PersistentPreference
44 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
45 import com.android.settingslib.metadata.PreferenceIconProvider
46 import com.android.settingslib.metadata.PreferenceLifecycleContext
47 import com.android.settingslib.metadata.PreferenceLifecycleProvider
48 import com.android.settingslib.metadata.PreferenceMetadata
49 import com.android.settingslib.metadata.RangeValue
50 import com.android.settingslib.metadata.ReadWritePermit
51 import com.android.settingslib.metadata.SensitivityLevel
52 import com.android.settingslib.preference.PreferenceBinding
53
54 // LINT.IfChange
55 open class SeparateRingVolumePreference :
56 PreferenceMetadata,
57 PreferenceBinding,
58 PersistentPreference<Int>,
59 RangeValue,
60 PreferenceAvailabilityProvider,
61 PreferenceIconProvider,
62 PreferenceLifecycleProvider,
63 PreferenceRestrictionMixin {
64
65 private var broadcastReceiver: BroadcastReceiver? = null
66
67 override val key: String
68 get() = KEY
69
70 override val title: Int
71 get() = R.string.separate_ring_volume_option_title
72
getIconnull73 override fun getIcon(context: Context) = context.getIconRes()
74
75 override fun isAvailable(context: Context) = !createAudioHelper(context).isSingleVolume
76
77 override fun isEnabled(context: Context) = super<PreferenceRestrictionMixin>.isEnabled(context)
78
79 override val restrictionKeys
80 get() = arrayOf(UserManager.DISALLOW_ADJUST_VOLUME)
81
82 override fun storage(context: Context): KeyValueStore {
83 val helper = createAudioHelper(context)
84 return object : NoOpKeyedObservable<String>(), KeyValueStore {
85 override fun contains(key: String) = key == KEY
86
87 @Suppress("UNCHECKED_CAST")
88 override fun <T : Any> getValue(key: String, valueType: Class<T>) =
89 helper.getStreamVolume(STREAM_RING) as T
90
91 override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
92 helper.setStreamVolume(STREAM_RING, value as Int)
93 }
94 }
95 }
96
getReadPermitnull97 override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
98 ReadWritePermit.ALLOW
99
100 override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
101 ReadWritePermit.ALLOW
102
103 override val sensitivityLevel
104 get() = SensitivityLevel.NO_SENSITIVITY
105
106 override fun getMinValue(context: Context) =
107 createAudioHelper(context).getMinVolume(STREAM_RING)
108
109 override fun getMaxValue(context: Context) =
110 createAudioHelper(context).getMaxVolume(STREAM_RING)
111
112 override fun createWidget(context: Context) = VolumeSeekBarPreference(context)
113
114 override fun bind(preference: Preference, metadata: PreferenceMetadata) {
115 super.bind(preference, metadata)
116 (preference as VolumeSeekBarPreference).apply {
117 setStream(STREAM_RING)
118 setMuteIcon(context.getIconRes())
119 updateContentDescription(context.getContentDescription())
120 setListener { updateContentDescription(context.getContentDescription()) }
121 setSuppressionText(context.getSuppressionText())
122 }
123 }
124
onStartnull125 override fun onStart(context: PreferenceLifecycleContext) {
126 super.onStart(context)
127 val receiver =
128 object : BroadcastReceiver() {
129 override fun onReceive(receiverContext: Context, intent: Intent) {
130 context.notifyPreferenceChange(KEY)
131 }
132 }
133 context.registerReceiver(
134 receiver,
135 IntentFilter().apply {
136 addAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED)
137 addAction(INTERNAL_RINGER_MODE_CHANGED_ACTION)
138 },
139 )
140 broadcastReceiver = receiver
141 }
142
onStopnull143 override fun onStop(context: PreferenceLifecycleContext) {
144 super.onStop(context)
145 broadcastReceiver?.let { context.unregisterReceiver(it) }
146 }
147
createAudioHelpernull148 open fun createAudioHelper(context: Context) = AudioHelper(context)
149
150 companion object {
151 const val KEY = "separate_ring_volume"
152 }
153 }
154
Contextnull155 fun Context.getContentDescription() =
156 when (getEffectiveRingerMode()) {
157 RINGER_MODE_VIBRATE -> getString(R.string.ringer_content_description_vibrate_mode)
158 RINGER_MODE_SILENT -> getString(R.string.ringer_content_description_silent_mode)
159 else -> getString(R.string.separate_ring_volume_option_title)
160 }
161
Contextnull162 fun Context.getIconRes() =
163 when (getEffectiveRingerMode()) {
164 RINGER_MODE_NORMAL -> R.drawable.ic_ring_volume
165 RINGER_MODE_VIBRATE -> R.drawable.ic_volume_ringer_vibrate
166 else -> R.drawable.ic_ring_volume_off
167 }
168
Contextnull169 fun Context.getEffectiveRingerMode(): Int {
170 val hasVibrator = getSystemService(Vibrator::class.java)?.hasVibrator() ?: false
171 val ringerMode =
172 getSystemService(AudioManager::class.java)?.getRingerModeInternal() ?: RINGER_MODE_NORMAL
173 return when {
174 !hasVibrator && ringerMode == RINGER_MODE_VIBRATE -> RINGER_MODE_SILENT
175 else -> ringerMode
176 }
177 }
178
Contextnull179 fun Context.getSuppressionText(): String? {
180 val suppressor = NotificationManager.from(this).getEffectsSuppressor()
181 val hints =
182 INotificationManager.Stub.asInterface(ServiceManager.getService(NOTIFICATION_SERVICE))
183 ?.hintsFromListenerNoToken ?: 0
184 return when {
185 (hints and HINT_HOST_DISABLE_CALL_EFFECTS) != 0 ||
186 (hints and HINT_HOST_DISABLE_EFFECTS) != 0 ->
187 SuppressorHelper.getSuppressionText(this, suppressor)
188 else -> null
189 }
190 }
191 // LINT.ThenChange(SeparateRingVolumePreferenceController.java)
192