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