• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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 
18 package com.android.systemui.keyguard.data.quickaffordance
19 
20 import android.content.Context
21 import android.media.AudioManager
22 import androidx.lifecycle.LiveData
23 import androidx.lifecycle.Observer
24 import com.android.app.tracing.coroutines.launchTraced as launch
25 import com.android.systemui.animation.Expandable
26 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
27 import com.android.systemui.common.shared.model.ContentDescription
28 import com.android.systemui.common.shared.model.Icon
29 import com.android.systemui.dagger.SysUISingleton
30 import com.android.systemui.dagger.qualifiers.Application
31 import com.android.systemui.dagger.qualifiers.Background
32 import com.android.systemui.dagger.qualifiers.Main
33 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
34 import com.android.systemui.res.R
35 import com.android.systemui.settings.UserFileManager
36 import com.android.systemui.settings.UserTracker
37 import com.android.systemui.shade.ShadeDisplayAware
38 import com.android.systemui.util.RingerModeTracker
39 import javax.inject.Inject
40 import kotlinx.coroutines.CoroutineDispatcher
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.channels.awaitClose
43 import kotlinx.coroutines.flow.Flow
44 import kotlinx.coroutines.flow.distinctUntilChanged
45 import kotlinx.coroutines.flow.flowOn
46 import kotlinx.coroutines.flow.map
47 import kotlinx.coroutines.flow.onEach
48 import kotlinx.coroutines.flow.onStart
49 import kotlinx.coroutines.withContext
50 
51 @SysUISingleton
52 class MuteQuickAffordanceConfig
53 @Inject
54 constructor(
55     @ShadeDisplayAware private val context: Context,
56     private val userTracker: UserTracker,
57     private val userFileManager: UserFileManager,
58     private val ringerModeTracker: RingerModeTracker,
59     private val audioManager: AudioManager,
60     @Application private val coroutineScope: CoroutineScope,
61     @Main private val mainDispatcher: CoroutineDispatcher,
62     @Background private val backgroundDispatcher: CoroutineDispatcher,
63 ) : KeyguardQuickAffordanceConfig {
64 
65     private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
66 
67     override val key: String = BuiltInKeyguardQuickAffordanceKeys.MUTE
68 
69     override fun pickerName(): String = context.getString(R.string.volume_ringer_status_silent)
70 
71     override val pickerIconResourceId: Int = R.drawable.ic_notifications_silence
72 
73     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
74         ringerModeTracker.ringerModeInternal
75             .asFlow()
76             .onStart { getLastNonSilentRingerMode() }
77             .distinctUntilChanged()
78             .onEach { mode ->
79                 // only remember last non-SILENT ringer mode
80                 if (mode != null && mode != AudioManager.RINGER_MODE_SILENT) {
81                     previousNonSilentMode = mode
82                 }
83             }
84             .map { mode ->
85                 val (activationState, contentDescriptionRes) =
86                     when {
87                         audioManager.isVolumeFixed ->
88                             ActivationState.NotSupported to R.string.volume_ringer_hint_mute
89                         mode == AudioManager.RINGER_MODE_SILENT ->
90                             ActivationState.Active to R.string.volume_ringer_hint_unmute
91                         else -> ActivationState.Inactive to R.string.volume_ringer_hint_mute
92                     }
93 
94                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
95                     Icon.Resource(
96                         R.drawable.ic_notifications_silence,
97                         ContentDescription.Resource(contentDescriptionRes),
98                     ),
99                     activationState,
100                 )
101             }
102             .flowOn(backgroundDispatcher)
103 
104     override fun onTriggered(
105         expandable: Expandable?
106     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
107         coroutineScope.launch(context = backgroundDispatcher) {
108             val newRingerMode: Int
109             val currentRingerMode = audioManager.ringerModeInternal
110             if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
111                 newRingerMode = previousNonSilentMode
112             } else {
113                 previousNonSilentMode = currentRingerMode
114                 newRingerMode = AudioManager.RINGER_MODE_SILENT
115             }
116 
117             if (currentRingerMode != newRingerMode) {
118                 audioManager.ringerModeInternal = newRingerMode
119             }
120         }
121         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
122     }
123 
124     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
125         withContext(backgroundDispatcher) {
126             if (audioManager.isVolumeFixed) {
127                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
128             } else {
129                 KeyguardQuickAffordanceConfig.PickerScreenState.Default()
130             }
131         }
132 
133     /**
134      * Gets the last non-silent ringer mode from shared-preferences if it exists. This is cached by
135      * [MuteQuickAffordanceCoreStartable] while this affordance is selected
136      */
137     private suspend fun getLastNonSilentRingerMode(): Int =
138         withContext(backgroundDispatcher) {
139             userFileManager
140                 .getSharedPreferences(
141                     MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
142                     Context.MODE_PRIVATE,
143                     userTracker.userId,
144                 )
145                 .getInt(
146                     LAST_NON_SILENT_RINGER_MODE_KEY,
147                     ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE,
148                 )
149         }
150 
151     private fun <T> LiveData<T>.asFlow(): Flow<T?> =
152         conflatedCallbackFlow {
153                 val observer = Observer { value: T -> trySend(value) }
154                 observeForever(observer)
155                 send(value)
156                 awaitClose { removeObserver(observer) }
157             }
158             .flowOn(mainDispatcher)
159 
160     companion object {
161         const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
162         const val MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME = "quick_affordance_mute_ringer_mode_cache"
163         private const val DEFAULT_LAST_NON_SILENT_VALUE = AudioManager.RINGER_MODE_NORMAL
164     }
165 }
166