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