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