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.Manifest.permission.MODIFY_AUDIO_SETTINGS
20 import android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
21 import android.app.INotificationManager
22 import android.app.NotificationManager
23 import android.app.NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED
24 import android.app.settings.SettingsEnums.ACTION_RING_VOLUME
25 import android.content.BroadcastReceiver
26 import android.content.Context
27 import android.content.Context.NOTIFICATION_SERVICE
28 import android.content.Intent
29 import android.content.IntentFilter
30 import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
31 import android.media.AudioManager
32 import android.media.AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION
33 import android.media.AudioManager.RINGER_MODE_NORMAL
34 import android.media.AudioManager.RINGER_MODE_SILENT
35 import android.media.AudioManager.RINGER_MODE_VIBRATE
36 import android.media.AudioManager.STREAM_RING
37 import android.os.ServiceManager
38 import android.os.UserManager
39 import android.os.Vibrator
40 import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS
41 import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS
42 import androidx.preference.Preference
43 import com.android.settings.R
44 import com.android.settings.contract.KEY_RING_VOLUME
45 import com.android.settings.metrics.PreferenceActionMetricsProvider
46 import com.android.settings.restriction.PreferenceRestrictionMixin
47 import com.android.settingslib.datastore.KeyValueStore
48 import com.android.settingslib.datastore.NoOpKeyedObservable
49 import com.android.settingslib.datastore.Permissions
50 import com.android.settingslib.datastore.and
51 import com.android.settingslib.metadata.IntRangeValuePreference
52 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
53 import com.android.settingslib.metadata.PreferenceIconProvider
54 import com.android.settingslib.metadata.PreferenceLifecycleContext
55 import com.android.settingslib.metadata.PreferenceLifecycleProvider
56 import com.android.settingslib.metadata.PreferenceMetadata
57 import com.android.settingslib.metadata.ReadWritePermit
58 import com.android.settingslib.metadata.SensitivityLevel
59 import com.android.settingslib.preference.PreferenceBinding
60
61 // LINT.IfChange
62 open class SeparateRingVolumePreference :
63 IntRangeValuePreference,
64 PreferenceBinding,
65 PreferenceActionMetricsProvider,
66 PreferenceAvailabilityProvider,
67 PreferenceIconProvider,
68 PreferenceLifecycleProvider,
69 PreferenceRestrictionMixin {
70
71 private var broadcastReceiver: BroadcastReceiver? = null
72
73 override val key: String
74 get() = KEY
75
76 override val title: Int
77 get() = R.string.separate_ring_volume_option_title
78
79 override val preferenceActionMetrics: Int
80 get() = ACTION_RING_VOLUME
81
tagsnull82 override fun tags(context: Context) = arrayOf(KEY_RING_VOLUME)
83
84 override fun getIcon(context: Context) = context.getIconRes()
85
86 override fun isAvailable(context: Context) = !createAudioHelper(context).isSingleVolume
87
88 override fun isEnabled(context: Context) = super<PreferenceRestrictionMixin>.isEnabled(context)
89
90 override val restrictionKeys
91 get() = arrayOf(UserManager.DISALLOW_ADJUST_VOLUME)
92
93 override fun storage(context: Context): KeyValueStore {
94 val helper = createAudioHelper(context)
95 return object : NoOpKeyedObservable<String>(), KeyValueStore {
96 override fun contains(key: String) = key == KEY
97
98 @Suppress("UNCHECKED_CAST")
99 override fun <T : Any> getValue(key: String, valueType: Class<T>) =
100 helper.getStreamVolume(STREAM_RING) as T
101
102 override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
103 helper.setStreamVolume(STREAM_RING, value as Int)
104 }
105 }
106 }
107
getReadPermissionsnull108 override fun getReadPermissions(context: Context) = Permissions.EMPTY
109
110 override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
111 ReadWritePermit.ALLOW
112
113 override fun getWritePermissions(context: Context): Permissions? {
114 var permissions = Permissions.allOf(MODIFY_AUDIO_SETTINGS)
115 if (context.packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE)) {
116 permissions = permissions and MODIFY_AUDIO_SETTINGS_PRIVILEGED
117 }
118 return permissions
119 }
120
getWritePermitnull121 override fun getWritePermit(context: Context, value: Int?, callingPid: Int, callingUid: Int) =
122 ReadWritePermit.ALLOW
123
124 override val sensitivityLevel
125 get() = SensitivityLevel.NO_SENSITIVITY
126
127 override fun getMinValue(context: Context) =
128 createAudioHelper(context).getMinVolume(STREAM_RING)
129
130 override fun getMaxValue(context: Context) =
131 createAudioHelper(context).getMaxVolume(STREAM_RING)
132
133 override fun createWidget(context: Context) = VolumeSeekBarPreference(context)
134
135 override fun bind(preference: Preference, metadata: PreferenceMetadata) {
136 super.bind(preference, metadata)
137 (preference as VolumeSeekBarPreference).apply {
138 setStream(STREAM_RING)
139 setMuteIcon(context.getIconRes())
140 updateContentDescription(context.getContentDescription())
141 setListener { updateContentDescription(context.getContentDescription()) }
142 setSuppressionText(context.getSuppressionText())
143 }
144 }
145
onStartnull146 override fun onStart(context: PreferenceLifecycleContext) {
147 super.onStart(context)
148 val receiver =
149 object : BroadcastReceiver() {
150 override fun onReceive(receiverContext: Context, intent: Intent) {
151 context.notifyPreferenceChange(KEY)
152 }
153 }
154 context.registerReceiver(
155 receiver,
156 IntentFilter().apply {
157 addAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED)
158 addAction(INTERNAL_RINGER_MODE_CHANGED_ACTION)
159 },
160 )
161 broadcastReceiver = receiver
162 }
163
onStopnull164 override fun onStop(context: PreferenceLifecycleContext) {
165 super.onStop(context)
166 broadcastReceiver?.let { context.unregisterReceiver(it) }
167 }
168
createAudioHelpernull169 open fun createAudioHelper(context: Context) = AudioHelper(context)
170
171 companion object {
172 const val KEY = "separate_ring_volume"
173 }
174 }
175
Contextnull176 fun Context.getContentDescription() =
177 when (getEffectiveRingerMode()) {
178 RINGER_MODE_VIBRATE -> getString(R.string.ringer_content_description_vibrate_mode)
179 RINGER_MODE_SILENT -> getString(R.string.ringer_content_description_silent_mode)
180 else -> getString(R.string.separate_ring_volume_option_title)
181 }
182
Contextnull183 fun Context.getIconRes() =
184 when (getEffectiveRingerMode()) {
185 RINGER_MODE_NORMAL -> R.drawable.ic_ring_volume
186 RINGER_MODE_VIBRATE -> R.drawable.ic_volume_ringer_vibrate
187 else -> R.drawable.ic_ring_volume_off
188 }
189
Contextnull190 fun Context.getEffectiveRingerMode(): Int {
191 val hasVibrator = getSystemService(Vibrator::class.java)?.hasVibrator() ?: false
192 val ringerMode =
193 getSystemService(AudioManager::class.java)?.getRingerModeInternal() ?: RINGER_MODE_NORMAL
194 return when {
195 !hasVibrator && ringerMode == RINGER_MODE_VIBRATE -> RINGER_MODE_SILENT
196 else -> ringerMode
197 }
198 }
199
Contextnull200 fun Context.getSuppressionText(): String? {
201 val suppressor = NotificationManager.from(this).getEffectsSuppressor()
202 val hints =
203 INotificationManager.Stub.asInterface(ServiceManager.getService(NOTIFICATION_SERVICE))
204 ?.hintsFromListenerNoToken ?: 0
205 return when {
206 (hints and HINT_HOST_DISABLE_CALL_EFFECTS) != 0 ||
207 (hints and HINT_HOST_DISABLE_EFFECTS) != 0 ->
208 SuppressorHelper.getSuppressionText(this, suppressor)
209 else -> null
210 }
211 }
212 // LINT.ThenChange(SeparateRingVolumePreferenceController.java)
213