summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alejandro Nijamkin <nijamkin@google.com> 2022-11-04 15:35:25 -0700
committer Alejandro Nijamkin <nijamkin@google.com> 2022-11-16 13:41:22 -0800
commit694e0ab10fe0ea76fd2370094fa9b25211ec2c94 (patch)
tree411caff051b530ce1f280b254dda66628e2828a5
parentd29f47f3fe072db18ec440c3045045101bbcb67e (diff)
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 <item>bottom_end:home,wallet</item> 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
-rw-r--r--packages/SystemUI/res/values/config.xml9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt1
8 files changed, 156 insertions, 12 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 88af1793d771..7a362040427a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -817,4 +817,13 @@
<item>bottom_end:1</item>
</string-array>
+ <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+ string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+ separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+ default is displayed by System UI as long as the user hasn't made a different choice for that
+ slot. If the user did make a choice, even if the choice is the "None" option, the default is
+ ignored. -->
+ <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ </string-array>
+
</resources>
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 d89ac37322b4..b29cf45cc709 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<String, List<String>> 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<Map<String, List<String>>> =
@@ -86,17 +100,35 @@ constructor(
*/
fun getSelections(): Map<String, List<String>> {
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<String>,
) {
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 2d0f9e264f69..5228e1774edf 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<UserFileManager>().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 b8bef1a9d6f3..d8ee9f113d33 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<String>())
val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
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<Map<String, List<String>>>()
+ 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<Map<String, List<String>>>()
+ 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<Map<String, List<String>>>()
+ 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<String, List<String>>?,
expected: Map<String, List<String>>,
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 a95f788b1ee9..e56e1b93dcfb 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<UserFileManager>().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 3e37459d8eeb..3b1cbb1bd620 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<UserFileManager>().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 f887bf93de8d..1374c43ebe1b 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<UserFileManager>().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 03e6ccb6fabe..abe8a304a4ef 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<UserFileManager>().apply {
whenever(