From 694e0ab10fe0ea76fd2370094fa9b25211ec2c94 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Fri, 4 Nov 2022 15:35:25 -0700 Subject: [PATCH] Defines config for default affordances. As per a request from the large screen team, adding a way for device configurations to be able to supply System UI with default affordances for slot IDs. The way it works is: 1. An OEM overrides the value of config_keyguardQuickAffordanceDefaults in config.xml with a list of key-value pairs. Each pair is a slot ID followed by one or more affordance IDs. 2. The KeyguardQuickAffordanceSelectionManager contains logic to default to those affordances in cases when the user hasn't yet selected anything for that slot. Once the user selects something for the slot, even the "None" option (which clears out the slot), the default is ignored. Fix: 257051288 Test: manually verified by temporarily adding bottom_end:home,wallet to the config.xml and seeing that, if I cleared data for system UI and restarted it, the home control affordance was visible (after I set it up through quick settings home tile, for course - because clearing data forgets that too). Also added unit tests. Change-Id: I63484a92cd8ed8bbcb6a331f500531c62a021f6c --- packages/SystemUI/res/values/config.xml | 9 ++ ...KeyguardQuickAffordanceSelectionManager.kt | 55 ++++++++--- .../KeyguardQuickAffordanceProviderTest.kt | 1 + ...uardQuickAffordanceSelectionManagerTest.kt | 99 ++++++++++++++++++- .../KeyguardQuickAffordanceRepositoryTest.kt | 1 + ...ckAffordanceInteractorParameterizedTest.kt | 1 + .../KeyguardQuickAffordanceInteractorTest.kt | 1 + .../KeyguardBottomAreaViewModelTest.kt | 1 + 8 files changed, 156 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 88af1793d7716..7a362040427a8 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -817,4 +817,13 @@ bottom_end:1 + + + + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt index d89ac37322b42..b29cf45cc7094 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt @@ -20,9 +20,11 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import android.content.SharedPreferences import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import javax.inject.Inject @@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.flatMapLatest class KeyguardQuickAffordanceSelectionManager @Inject constructor( + @Application context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, ) { @@ -63,6 +66,17 @@ constructor( awaitClose { userTracker.removeCallback(callback) } } + private val defaults: Map> by lazy { + context.resources + .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) + .associate { item -> + val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER) + check(splitUp.size == 2) + val slotId = splitUp[0] + val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER) + slotId to affordanceIds + } + } /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */ val selections: Flow>> = @@ -86,17 +100,35 @@ constructor( */ fun getSelections(): Map> { val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) } - return slotKeys.associate { key -> - val slotId = key.substring(KEY_PREFIX_SLOT.length) - val value = sharedPrefs.getString(key, null) - val affordanceIds = - if (!value.isNullOrEmpty()) { - value.split(DELIMITER) - } else { - emptyList() + val result = + slotKeys + .associate { key -> + val slotId = key.substring(KEY_PREFIX_SLOT.length) + val value = sharedPrefs.getString(key, null) + val affordanceIds = + if (!value.isNullOrEmpty()) { + value.split(AFFORDANCE_DELIMITER) + } else { + emptyList() + } + slotId to affordanceIds } - slotId to affordanceIds + .toMutableMap() + + // If the result map is missing keys, it means that the system has never set anything for + // those slots. This is where we need examine our defaults and see if there should be a + // default value for the affordances in the slot IDs that are missing from the result. + // + // Once the user makes any selection for a slot, even when they select "None", this class + // will persist a key for that slot ID. In the case of "None", it will have a value of the + // empty string. This is why this system works. + defaults.forEach { (slotId, affordanceIds) -> + if (!result.containsKey(slotId)) { + result[slotId] = affordanceIds + } } + + return result } /** @@ -108,7 +140,7 @@ constructor( affordanceIds: List, ) { val key = "$KEY_PREFIX_SLOT$slotId" - val value = affordanceIds.joinToString(DELIMITER) + val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER) sharedPrefs.edit().putString(key, value).apply() } @@ -116,6 +148,7 @@ constructor( private const val TAG = "KeyguardQuickAffordanceSelectionManager" @VisibleForTesting const val FILE_NAME = "quick_affordance_selections" private const val KEY_PREFIX_SLOT = "slot_" - private const val DELIMITER = "," + private const val SLOT_AFFORDANCES_DELIMITER = ":" + private const val AFFORDANCE_DELIMITER = "," } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt index 2d0f9e264f692..5228e1774edf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt @@ -77,6 +77,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { scope = CoroutineScope(IMMEDIATE), selectionManager = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = mock().apply { whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt index b8bef1a9d6f36..d8ee9f113d33c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.SharedPreferences import android.content.pm.UserInfo -import androidx.test.core.app.ActivityScenario.launch import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager @@ -63,6 +63,7 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = userFileManager, userTracker = userTracker, ) @@ -70,6 +71,7 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { @Test fun setSelections() = runTest { + overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf()) val affordanceIdsBySlotId = mutableListOf>>() val job = launch(UnconfinedTestDispatcher()) { @@ -221,6 +223,101 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { job.cancel() } + @Test + fun `selections respects defaults`() = runTest { + val slotId1 = "slot1" + val slotId2 = "slot2" + val affordanceId1 = "affordance1" + val affordanceId2 = "affordance2" + val affordanceId3 = "affordance3" + overrideResource( + R.array.config_keyguardQuickAffordanceDefaults, + arrayOf( + "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}", + "$slotId2:${listOf(affordanceId2).joinToString(",")}", + ), + ) + val affordanceIdsBySlotId = mutableListOf>>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.selections.toList(affordanceIdsBySlotId) + } + + assertSelections( + affordanceIdsBySlotId.last(), + mapOf( + slotId1 to listOf(affordanceId1, affordanceId3), + slotId2 to listOf(affordanceId2), + ), + ) + + job.cancel() + } + + @Test + fun `selections ignores defaults after selecting an affordance`() = runTest { + val slotId1 = "slot1" + val slotId2 = "slot2" + val affordanceId1 = "affordance1" + val affordanceId2 = "affordance2" + val affordanceId3 = "affordance3" + overrideResource( + R.array.config_keyguardQuickAffordanceDefaults, + arrayOf( + "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}", + "$slotId2:${listOf(affordanceId2).joinToString(",")}", + ), + ) + val affordanceIdsBySlotId = mutableListOf>>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.selections.toList(affordanceIdsBySlotId) + } + + underTest.setSelections(slotId1, listOf(affordanceId2)) + assertSelections( + affordanceIdsBySlotId.last(), + mapOf( + slotId1 to listOf(affordanceId2), + slotId2 to listOf(affordanceId2), + ), + ) + + job.cancel() + } + + @Test + fun `selections ignores defaults after clearing a slot`() = runTest { + val slotId1 = "slot1" + val slotId2 = "slot2" + val affordanceId1 = "affordance1" + val affordanceId2 = "affordance2" + val affordanceId3 = "affordance3" + overrideResource( + R.array.config_keyguardQuickAffordanceDefaults, + arrayOf( + "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}", + "$slotId2:${listOf(affordanceId2).joinToString(",")}", + ), + ) + val affordanceIdsBySlotId = mutableListOf>>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.selections.toList(affordanceIdsBySlotId) + } + + underTest.setSelections(slotId1, listOf()) + assertSelections( + affordanceIdsBySlotId.last(), + mapOf( + slotId1 to listOf(), + slotId2 to listOf(affordanceId2), + ), + ) + + job.cancel() + } + private fun assertSelections( observed: Map>?, expected: Map>, 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 a95f788b1ee91..e56e1b93dcfb6 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 @@ -64,6 +64,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { scope = CoroutineScope(IMMEDIATE), selectionManager = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = mock().apply { whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 3e37459d8eeb9..3b1cbb1bd6203 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -240,6 +240,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { scope = CoroutineScope(IMMEDIATE), selectionManager = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = mock().apply { whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index f887bf93de8d1..1374c43ebe1ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -100,6 +100,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { scope = CoroutineScope(IMMEDIATE), selectionManager = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = mock().apply { whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 03e6ccb6fabe2..abe8a304a4ef7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -124,6 +124,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { scope = CoroutineScope(IMMEDIATE), selectionManager = KeyguardQuickAffordanceSelectionManager( + context = context, userFileManager = mock().apply { whenever(