• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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