Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 694e0ab1 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

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
parent d29f47f3
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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>
+44 −11
Original line number Diff line number Diff line
@@ -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 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(DELIMITER)
                            value.split(AFFORDANCE_DELIMITER)
                        } else {
                            emptyList()
                        }
                    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 = ","
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
                scope = CoroutineScope(IMMEDIATE),
                selectionManager =
                    KeyguardQuickAffordanceSelectionManager(
                        context = context,
                        userFileManager =
                            mock<UserFileManager>().apply {
                                whenever(
+98 −1
Original line number Diff line number Diff line
@@ -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>>,
+1 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
                scope = CoroutineScope(IMMEDIATE),
                selectionManager =
                    KeyguardQuickAffordanceSelectionManager(
                        context = context,
                        userFileManager =
                            mock<UserFileManager>().apply {
                                whenever(
Loading