summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Brad Hinegardner <bhinegardner@google.com> 2023-01-30 19:21:55 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-01-30 19:21:55 +0000
commitd886dac36c32d04479f47f7a0631d2f0998564d7 (patch)
tree4b9291b291674263cfbe91a7fa74ab3f6365eeca
parent3944b8312388c06901e0c5434e3462150bd85f65 (diff)
parent0b185b890780305c74f0cbe4300c13de846971de (diff)
Merge "Add Mute custom quick affordance" into tm-qpr-dev
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt144
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt140
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt220
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt4
10 files changed, 612 insertions, 10 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index ef07e996667c..2dfcf70177fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -31,6 +31,7 @@ import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
import com.android.systemui.log.SessionTracker
import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
import com.android.systemui.media.RingtonePlayer
@@ -288,6 +289,14 @@ abstract class SystemUICoreStartableModule {
@ClassKey(StylusUsiPowerStartable::class)
abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable
+ /** Inject into MuteQuickAffordanceCoreStartable*/
+ @Binds
+ @IntoMap
+ @ClassKey(MuteQuickAffordanceCoreStartable::class)
+ abstract fun bindMuteQuickAffordanceCoreStartable(
+ sysui: MuteQuickAffordanceCoreStartable
+ ): CoreStartable
+
/**Inject into DreamMonitor */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index 76c2430e37ad..80675d373b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -29,6 +29,7 @@ object BuiltInKeyguardQuickAffordanceKeys {
const val DO_NOT_DISTURB = "do_not_disturb"
const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
+ const val MUTE = "mute"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
const val VIDEO_CAMERA = "video_camera"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index a1cce5c670ba..45561959a7df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -33,12 +33,13 @@ interface KeyguardDataQuickAffordanceModule {
@Provides
@ElementsIntoSet
fun quickAffordanceConfigs(
+ camera: CameraQuickAffordanceConfig,
doNotDisturb: DoNotDisturbQuickAffordanceConfig,
flashlight: FlashlightQuickAffordanceConfig,
home: HomeControlsKeyguardQuickAffordanceConfig,
+ mute: MuteQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
- camera: CameraQuickAffordanceConfig,
videoCamera: VideoCameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
@@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule {
doNotDisturb,
flashlight,
home,
+ mute,
quickAccessWallet,
qrCodeScanner,
videoCamera,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..d085db9a1eda
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+@SysUISingleton
+class MuteQuickAffordanceConfig @Inject constructor(
+ context: Context,
+ private val userTracker: UserTracker,
+ private val userFileManager: UserFileManager,
+ private val ringerModeTracker: RingerModeTracker,
+ private val audioManager: AudioManager,
+) : KeyguardQuickAffordanceConfig {
+
+ private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.MUTE
+
+ override val pickerName: String = context.getString(R.string.volume_ringer_status_silent)
+
+ override val pickerIconResourceId: Int = R.drawable.ic_notifications_silence
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ ringerModeTracker.ringerModeInternal.asFlow()
+ .onStart { emit(getLastNonSilentRingerMode()) }
+ .distinctUntilChanged()
+ .onEach { mode ->
+ // only remember last non-SILENT ringer mode
+ if (mode != null && mode != AudioManager.RINGER_MODE_SILENT) {
+ previousNonSilentMode = mode
+ }
+ }
+ .map { mode ->
+ val (activationState, contentDescriptionRes) = when {
+ audioManager.isVolumeFixed ->
+ ActivationState.NotSupported to
+ R.string.volume_ringer_hint_mute
+ mode == AudioManager.RINGER_MODE_SILENT ->
+ ActivationState.Active to
+ R.string.volume_ringer_hint_mute
+ else ->
+ ActivationState.Inactive to
+ R.string.volume_ringer_hint_unmute
+ }
+
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_notifications_silence,
+ ContentDescription.Resource(contentDescriptionRes),
+ ),
+ activationState,
+ )
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ val newRingerMode: Int
+ val currentRingerMode =
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ newRingerMode = previousNonSilentMode
+ } else {
+ previousNonSilentMode = currentRingerMode
+ newRingerMode = AudioManager.RINGER_MODE_SILENT
+ }
+
+ if (currentRingerMode != newRingerMode) {
+ audioManager.ringerModeInternal = newRingerMode
+ }
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+ if (audioManager.isVolumeFixed) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ }
+
+ /**
+ * Gets the last non-silent ringer mode from shared-preferences if it exists. This is
+ * cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
+ */
+ private fun getLastNonSilentRingerMode(): Int =
+ userFileManager.getSharedPreferences(
+ MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ ).getInt(
+ LAST_NON_SILENT_RINGER_MODE_KEY,
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ )
+
+ private fun <T> LiveData<T>.asFlow(): Flow<T?> =
+ conflatedCallbackFlow {
+ val observer = Observer { value: T -> trySend(value) }
+ observeForever(observer)
+ send(value)
+ awaitClose { removeObserver(observer) }
+ }
+
+ companion object {
+ const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
+ const val MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME = "quick_affordance_mute_ringer_mode_cache"
+ private const val DEFAULT_LAST_NON_SILENT_VALUE = AudioManager.RINGER_MODE_NORMAL
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
new file mode 100644
index 000000000000..12a6310ccc85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.Observer
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+/**
+ * Store previous non-silent Ringer Mode into shared prefs to be used for Mute Lockscreen Shortcut
+ */
+@SysUISingleton
+class MuteQuickAffordanceCoreStartable @Inject constructor(
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker,
+ private val ringerModeTracker: RingerModeTracker,
+ private val userFileManager: UserFileManager,
+ private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
+ @Application private val coroutineScope: CoroutineScope,
+) : CoreStartable {
+
+ private val observer = Observer(this::updateLastNonSilentRingerMode)
+
+ override fun start() {
+ if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
+
+ // only listen to ringerModeInternal changes when Mute is one of the selected affordances
+ keyguardQuickAffordanceRepository
+ .selections
+ .map { selections ->
+ // determines if Mute is selected in any lockscreen shortcut position
+ val muteSelected: Boolean = selections.values.any { configList ->
+ configList.any { config ->
+ config.key == BuiltInKeyguardQuickAffordanceKeys.MUTE
+ }
+ }
+ if (muteSelected) {
+ ringerModeTracker.ringerModeInternal.observeForever(observer)
+ } else {
+ ringerModeTracker.ringerModeInternal.removeObserver(observer)
+ }
+ }
+ .launchIn(coroutineScope)
+ }
+
+ private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
+ if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+ userFileManager.getSharedPreferences(
+ MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ .edit()
+ .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+ .apply()
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 2b2b9d0703fa..8ece3183fd56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -146,7 +146,7 @@ constructor(
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
- fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -155,7 +155,7 @@ constructor(
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
- fun getSelections(): Map<String, List<String>> {
+ fun getCurrentSelections(): Map<String, List<String>> {
return selectionManager.value.getSelections()
}
@@ -217,7 +217,7 @@ constructor(
private inner class Dumpster : Dumpable {
override fun dump(pw: PrintWriter, args: Array<out String>) {
val slotPickerRepresentations = getSlotPickerRepresentations()
- val selectionsBySlotId = getSelections()
+ val selectionsBySlotId = getCurrentSelections()
pw.println("Slots & selections:")
slotPickerRepresentations.forEach { slotPickerRepresentation ->
val slotId = slotPickerRepresentation.id
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9ddc575307b5..57c3b3143c62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -154,7 +154,7 @@ constructor(
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
- repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -193,7 +193,7 @@ constructor(
if (affordanceId.isNullOrEmpty()) {
return if (
- repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).isEmpty()
) {
false
} else {
@@ -203,7 +203,7 @@ constructor(
}
val selections =
- repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
@@ -220,7 +220,7 @@ constructor(
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
val slots = repository.get().getSlotPickerRepresentations()
- val selections = repository.get().getSelections()
+ val selections = repository.get().getCurrentSelections()
val affordanceById =
getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
return slots.associate { slot ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..a3740d88e1a9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.LiveData
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceConfigTest : SysuiTestCase() {
+
+ private lateinit var underTest: MuteQuickAffordanceConfig
+ @Mock
+ private lateinit var ringerModeTracker: RingerModeTracker
+ @Mock
+ private lateinit var audioManager: AudioManager
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testScope = TestScope()
+
+ whenever(userTracker.userContext).thenReturn(context)
+ whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+ .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE))
+
+ underTest = MuteQuickAffordanceConfig(
+ context,
+ userTracker,
+ userFileManager,
+ ringerModeTracker,
+ audioManager
+ )
+ }
+
+ @Test
+ fun `picker state - volume fixed - not available`() = testScope.runTest {
+ //given
+ whenever(audioManager.isVolumeFixed).thenReturn(true)
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result)
+ }
+
+ @Test
+ fun `picker state - volume not fixed - available`() = testScope.runTest {
+ //given
+ whenever(audioManager.isVolumeFixed).thenReturn(false)
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result)
+ }
+
+ @Test
+ fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() {
+ //given
+ val ringerModeCapture = argumentCaptor<Int>()
+ val ringerModeInternal = mock<LiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ underTest.onTriggered(null)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT)
+
+ //when
+ val result = underTest.onTriggered(null)
+ verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value)
+ }
+
+ @Test
+ fun `triggered - state is not SILENT - move to SILENT ringer`() {
+ //given
+ val ringerModeCapture = argumentCaptor<Int>()
+ val ringerModeInternal = mock<LiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+
+ //when
+ val result = underTest.onTriggered(null)
+ verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
+
+ //then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
new file mode 100644
index 000000000000..26601b63e02d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var ringerModeTracker: RingerModeTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
+ @Mock
+ private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
+
+ private lateinit var testScope: TestScope
+
+ private lateinit var underTest: MuteQuickAffordanceCoreStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
+
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+
+ val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+
+ testScope = TestScope()
+
+ underTest = MuteQuickAffordanceCoreStartable(
+ featureFlags,
+ userTracker,
+ ringerModeTracker,
+ userFileManager,
+ keyguardQuickAffordanceRepository,
+ testScope,
+ )
+ }
+
+ @Test
+ fun `feature flag is OFF - do nothing with keyguardQuickAffordanceRepository`() = testScope.runTest {
+ //given
+ whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
+
+ //when
+ underTest.start()
+
+ //then
+ verifyZeroInteractions(keyguardQuickAffordanceRepository)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `feature flag is ON - call to keyguardQuickAffordanceRepository`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(keyguardQuickAffordanceRepository).selections
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `ringer mode is changed to SILENT - do not save to shared preferences`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ val observerCaptor = argumentCaptor<Observer<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+ verify(ringerModeInternal).observeForever(observerCaptor.capture())
+ observerCaptor.value.onChanged(AudioManager.RINGER_MODE_SILENT)
+
+ //then
+ verifyZeroInteractions(userFileManager)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `ringerModeInternal changes to something not SILENT - is set in sharedpreferences`() = testScope.runTest {
+ //given
+ val newRingerMode = 99
+ val observerCaptor = argumentCaptor<Observer<Int>>()
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ val sharedPrefs = context.getSharedPreferences("quick_affordance_mute_ringer_mode_cache_test", Context.MODE_PRIVATE)
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+ whenever(
+ userFileManager.getSharedPreferences(eq("quick_affordance_mute_ringer_mode_cache"), any(), any())
+ ).thenReturn(sharedPrefs)
+
+ //when
+ underTest.start()
+ runCurrent()
+ verify(ringerModeInternal).observeForever(observerCaptor.capture())
+ observerCaptor.value.onChanged(newRingerMode)
+ val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
+
+ //then
+ assertEquals(newRingerMode, result)
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is in selections - observe ringerModeInternal`() = testScope.runTest {
+ //given
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).observeForever(any())
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is in selections 2x - observe ringerModeInternal`() = testScope.runTest {
+ //given
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+ val emission = MutableStateFlow(mapOf("testKey" to listOf(config), "testkey2" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).observeForever(any())
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `MUTE is not in selections - stop observing ringerModeInternal`() = testScope.runTest {
+ //given
+ val config: KeyguardQuickAffordanceConfig = mock()
+ whenever(config.key).thenReturn("notmutequickaffordance")
+ val emission = MutableStateFlow(mapOf("testKey" to listOf(config)))
+ whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+ val ringerModeInternal = mock<MutableLiveData<Int>>()
+ whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+ //when
+ underTest.start()
+ runCurrent()
+
+ //then
+ verify(ringerModeInternal).removeObserver(any())
+ coroutineContext.cancelChildren()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index b071a028865d..6099f011a90d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -275,10 +275,10 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
assertThat(observed).isEqualTo(expected)
- assertThat(underTest.getSelections())
+ assertThat(underTest.getCurrentSelections())
.isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
expected.forEach { (slotId, configs) ->
- assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+ assertThat(underTest.getCurrentSelections(slotId)).isEqualTo(configs)
}
}