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