Merge "Multi-user customizable quick affordances." into tm-qpr-dev
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 3013227c..072cfb1 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
@@ -17,27 +17,35 @@
package com.android.systemui.keyguard.data.quickaffordance
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
@Module
-object KeyguardDataQuickAffordanceModule {
- @Provides
- @ElementsIntoSet
- fun quickAffordanceConfigs(
- flashlight: FlashlightQuickAffordanceConfig,
- home: HomeControlsKeyguardQuickAffordanceConfig,
- quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
- qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
- camera: CameraQuickAffordanceConfig,
- ): Set<KeyguardQuickAffordanceConfig> {
- return setOf(
- camera,
- flashlight,
- home,
- quickAccessWallet,
- qrCodeScanner,
- )
+interface KeyguardDataQuickAffordanceModule {
+ @Binds
+ fun providerClientFactory(
+ impl: KeyguardQuickAffordanceProviderClientFactoryImpl,
+ ): KeyguardQuickAffordanceProviderClientFactory
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ flashlight: FlashlightQuickAffordanceConfig,
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ camera,
+ flashlight,
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
index 766096f..72747f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -67,7 +67,7 @@
@Application private val scope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettings: SecureSettings,
- private val selectionsManager: KeyguardQuickAffordanceSelectionManager,
+ private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager,
) {
companion object {
private val BINDINGS =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
new file mode 100644
index 0000000..0066785
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.content.IntentFilter
+import android.content.SharedPreferences
+import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for the user associated with the
+ * System UI process.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceLocalUserSelectionManager
+@Inject
+constructor(
+ @Application context: Context,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ 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
+ }
+ }
+
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
+ override val selections: Flow<Map<String, List<String>>> =
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even if we never get a Backup & Restore restoration event (which is the most
+ // common case anyway as restoration really only happens on initial device
+ // setup).
+ emit(Unit)
+ }
+ ) { _, _ -> }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user ID changes or we have a backup & restore restoration event. The reason
+ // is that our sharedPrefs instance needs to be replaced with a new one as it
+ // depends on the user ID and when the B&R job completes, the backing file is
+ // replaced but the existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
+
+ override fun getSelections(): Map<String, List<String>> {
+ val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+ 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
+ }
+ .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
+ }
+
+ override fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ val key = "$KEY_PREFIX_SLOT$slotId"
+ val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+ sharedPrefs.edit().putString(key, value).apply()
+ }
+
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordancePrimaryUserSelectionManager"
+ const val FILE_NAME = "quick_affordance_selections"
+ private const val KEY_PREFIX_SLOT = "slot_"
+ private const val SLOT_AFFORDANCES_DELIMITER = ":"
+ private const val AFFORDANCE_DELIMITER = ","
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..727a813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+interface KeyguardQuickAffordanceProviderClientFactory {
+ fun create(): KeyguardQuickAffordanceProviderClient
+}
+
+class KeyguardQuickAffordanceProviderClientFactoryImpl
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClientFactory {
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return KeyguardQuickAffordanceProviderClientImpl(
+ context = userTracker.userContext,
+ backgroundDispatcher = backgroundDispatcher,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
new file mode 100644
index 0000000..8ffef25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.os.UserHandle
+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.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for users associated with other System
+ * UI processes.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceRemoteUserSelectionManager
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val userTracker: UserTracker,
+ private val clientFactory: KeyguardQuickAffordanceProviderClientFactory,
+ private val userHandle: UserHandle,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val clientOrNull: StateFlow<KeyguardQuickAffordanceProviderClient?> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.isSystem && userHandle.identifier != selectedUserId) {
+ clientFactory.create()
+ } else {
+ null
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ private val _selections: StateFlow<Map<String, List<String>>> =
+ clientOrNull
+ .flatMapLatest { client ->
+ client?.observeSelections()?.map { selections ->
+ buildMap<String, List<String>> {
+ selections.forEach { selection ->
+ val slotId = selection.slotId
+ val affordanceIds = (get(slotId) ?: emptyList()).toMutableList()
+ affordanceIds.add(selection.affordanceId)
+ put(slotId, affordanceIds)
+ }
+ }
+ }
+ ?: emptyFlow()
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ override val selections: Flow<Map<String, List<String>>> = _selections
+
+ override fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ override fun setSelections(slotId: String, affordanceIds: List<String>) {
+ clientOrNull.value?.let { client ->
+ scope.launch {
+ client.deleteAllSelections(slotId = slotId)
+ affordanceIds.forEach { affordanceId ->
+ client.insertSelection(slotId = slotId, affordanceId = affordanceId)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager"
+ }
+}
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 4f37e5f..21fffed 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
@@ -17,153 +17,22 @@
package com.android.systemui.keyguard.data.quickaffordance
-import android.content.Context
-import android.content.IntentFilter
-import android.content.SharedPreferences
-import com.android.systemui.R
-import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
-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
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.onStart
/**
- * Manages and provides access to the current "selections" of keyguard quick affordances, answering
- * the question "which affordances should the keyguard show?".
+ * Defines interface for classes that manage and provide access to the current "selections" of
+ * keyguard quick affordances, answering the question "which affordances should the keyguard show?".
*/
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class KeyguardQuickAffordanceSelectionManager
-@Inject
-constructor(
- @Application context: Context,
- private val userFileManager: UserFileManager,
- private val userTracker: UserTracker,
- broadcastDispatcher: BroadcastDispatcher,
-) {
-
- private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
-
- private val userId: Flow<Int> = conflatedCallbackFlow {
- val callback =
- object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- trySendWithFailureLogging(newUser, TAG)
- }
- }
-
- userTracker.addCallback(callback) { it.run() }
- trySendWithFailureLogging(userTracker.userId, TAG)
-
- 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
- }
- }
-
- /**
- * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
- * initial value.
- */
- private val backupRestorationEvents: Flow<Unit> =
- broadcastDispatcher.broadcastFlow(
- filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
- flags = Context.RECEIVER_NOT_EXPORTED,
- permission = BackupHelper.PERMISSION_SELF,
- )
+interface KeyguardQuickAffordanceSelectionManager {
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
- val selections: Flow<Map<String, List<String>>> =
- combine(
- userId,
- backupRestorationEvents.onStart {
- // We emit an initial event to make sure that the combine emits at least once,
- // even
- // if we never get a Backup & Restore restoration event (which is the most
- // common
- // case anyway as restoration really only happens on initial device setup).
- emit(Unit)
- }
- ) { _, _ ->
- }
- .flatMapLatest {
- conflatedCallbackFlow {
- // We want to instantiate a new SharedPreferences instance each time either the
- // user
- // ID changes or we have a backup & restore restoration event. The reason is
- // that
- // our sharedPrefs instance needs to be replaced with a new one as it depends on
- // the
- // user ID and when the B&R job completes, the backing file is replaced but the
- // existing instance still has a stale in-memory cache.
- sharedPrefs = instantiateSharedPrefs()
-
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
- }
- }
+ val selections: Flow<Map<String, List<String>>>
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
- fun getSelections(): Map<String, List<String>> {
- val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
- 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
- }
- .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
- }
+ fun getSelections(): Map<String, List<String>>
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
@@ -172,25 +41,9 @@
fun setSelections(
slotId: String,
affordanceIds: List<String>,
- ) {
- val key = "$KEY_PREFIX_SLOT$slotId"
- val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
- sharedPrefs.edit().putString(key, value).apply()
- }
-
- private fun instantiateSharedPrefs(): SharedPreferences {
- return userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
- }
+ )
companion object {
- private const val TAG = "KeyguardQuickAffordanceSelectionManager"
const val FILE_NAME = "quick_affordance_selections"
- private const val KEY_PREFIX_SLOT = "slot_"
- private const val SLOT_AFFORDANCES_DELIMITER = ":"
- private const val AFFORDANCE_DELIMITER = ","
}
}
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 d95a1a7..e3f5e90 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
@@ -18,45 +18,93 @@
package com.android.systemui.keyguard.data.repository
import android.content.Context
+import android.os.UserHandle
import com.android.systemui.Dumpable
import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Abstracts access to application state related to keyguard quick affordances. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
@Application private val appContext: Context,
@Application private val scope: CoroutineScope,
- private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+ private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
+ private val userTracker: UserTracker,
legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
dumpManager: DumpManager,
+ userHandle: UserHandle,
) {
+ private val userId: Flow<Int> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val selectionManager: StateFlow<KeyguardQuickAffordanceSelectionManager> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.identifier == selectedUserId) {
+ localUserSelectionManager
+ } else {
+ remoteUserSelectionManager
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = localUserSelectionManager,
+ )
+
/**
* List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
* given ID. The configs are sorted in descending priority order.
*/
val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
- selectionManager.selections
- .map { selectionsBySlotId ->
- selectionsBySlotId.mapValues { (_, selections) ->
- configs.filter { selections.contains(it.key) }
+ selectionManager
+ .flatMapLatest { selectionManager ->
+ selectionManager.selections.map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
}
}
.stateIn(
@@ -99,7 +147,7 @@
* slot with the given ID. The configs are sorted in descending priority order.
*/
fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
- val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -108,7 +156,7 @@
* are sorted in descending priority order.
*/
fun getSelections(): Map<String, List<String>> {
- return selectionManager.getSelections()
+ return selectionManager.value.getSelections()
}
/**
@@ -119,7 +167,7 @@
slotId: String,
affordanceIds: List<String>,
) {
- selectionManager.setSelections(
+ selectionManager.value.setSelections(
slotId = slotId,
affordanceIds = affordanceIds,
)
@@ -188,6 +236,7 @@
}
companion object {
+ private const val TAG = "KeyguardQuickAffordanceRepository"
private const val SLOT_CONFIG_DELIMITER = ":"
}
}
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 ee7154f..748c6e8 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
@@ -190,8 +190,6 @@
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
- check(isUsingRepository)
-
val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getSelections()
val affordanceById =
@@ -312,8 +310,6 @@
suspend fun getAffordancePickerRepresentations():
List<KeyguardQuickAffordancePickerRepresentation> {
- check(isUsingRepository)
-
return repository.get().getAffordancePickerRepresentations()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0c72b78..2b29885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.user;
import android.app.Activity;
+import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
@@ -51,4 +52,22 @@
@IntoMap
@ClassKey(UserSwitcherActivity.class)
public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
+
+ /**
+ * Provides the {@link UserHandle} for the user associated with this System UI process.
+ *
+ * <p>Note that this is static and unchanging for the life-time of the process we are running
+ * in. It can be <i>different</i> from the user that is the currently-selected user, which may
+ * be associated with a different System UI process.
+ *
+ * <p>For example, the System UI process which creates all the windows and renders UI is always
+ * the one associated with the primary user on the device. However, if the user is switched to
+ * another, non-primary user (for example user "X"), then a secondary System UI process will be
+ * spawned. While the original primary user process continues to be the only one rendering UI,
+ * the new system UI process may be used for things like file or content access.
+ */
+ @Provides
+ public static UserHandle provideUserHandle() {
+ return new UserHandle(UserHandle.myUserId());
+ }
}
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 32c5b3f..cef452b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -20,6 +20,7 @@
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SystemUIAppComponentFactoryBase
@@ -27,8 +28,10 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -74,8 +77,8 @@
underTest = KeyguardQuickAffordanceProvider()
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -91,11 +94,20 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
configs =
setOf(
FakeKeyguardQuickAffordanceConfig(
@@ -114,9 +126,10 @@
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest.interactor =
KeyguardQuickAffordanceInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 552b8cb..3b0169d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -57,7 +57,7 @@
private lateinit var testScope: TestScope
private lateinit var testDispatcher: TestDispatcher
- private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager
+ private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
private lateinit var settings: FakeSettings
@Before
@@ -75,7 +75,7 @@
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock {
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/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 6a2376b..67091a9 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/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -52,11 +52,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
@Mock private lateinit var userFileManager: UserFileManager
- private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+ private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager
private lateinit var userTracker: FakeUserTracker
private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
@@ -74,7 +74,7 @@
Dispatchers.setMain(dispatcher)
underTest =
- KeyguardQuickAffordanceSelectionManager(
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
new file mode 100644
index 0000000..d7e9cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+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.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
+
+ @Mock private lateinit var userHandle: UserHandle
+
+ private lateinit var underTest: KeyguardQuickAffordanceRemoteUserSelectionManager
+
+ private lateinit var clientFactory: FakeKeyguardQuickAffordanceProviderClientFactory
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM)
+ whenever(userHandle.isSystem).thenReturn(true)
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+
+ userTracker = FakeUserTracker()
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = 0,
+ )
+
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ OTHER_USER_ID_1 -> client1
+ OTHER_USER_ID_2 -> client2
+ else -> error("No client set-up for user $selectedUserId!")
+ }
+ }
+
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ underTest =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = testScope.backgroundScope,
+ userTracker = userTracker,
+ clientFactory = clientFactory,
+ userHandle = userHandle,
+ )
+ }
+
+ @Test
+ fun `selections - primary user process`() =
+ testScope.runTest {
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 2,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections - secondary user process - always empty`() =
+ testScope.runTest {
+ whenever(userHandle.isSystem).thenReturn(false)
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun setSelections() =
+ testScope.runTest {
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+
+ underTest.setSelections(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1),
+ )
+ runCurrent()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+ }
+
+ companion object {
+ private const val OTHER_USER_ID_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+ private const val OTHER_USER_ID_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
+ }
+}
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 652fae9..c40488a 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
@@ -17,17 +17,23 @@
package com.android.systemui.keyguard.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -39,6 +45,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,14 +62,24 @@
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
@Before
fun setUp() {
- config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
- config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ config1 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1
+ )
+ config2 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2
+ )
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ userTracker = FakeUserTracker()
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -75,24 +92,45 @@
)
.thenReturn(FakeSharedPreferences())
},
- userTracker = FakeUserTracker(),
+ userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ SECONDARY_USER_1 -> client1
+ SECONDARY_USER_2 -> client2
+ else -> error("No set-up client for user $selectedUserId!")
+ }
+ },
+ userHandle = UserHandle.SYSTEM,
+ )
underTest =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(config1, config2),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
}
@@ -187,7 +225,53 @@
)
}
- private suspend fun assertSelections(
+ @Test
+ fun `selections for secondary user`() =
+ runBlocking(IMMEDIATE) {
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = 2,
+ )
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+ val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
+ val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
+ yield()
+
+ assertSelections(
+ observed = observed.last(),
+ expected =
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ config2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private fun assertSelections(
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
@@ -201,5 +285,7 @@
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+ private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
}
}
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 ba7c40b..1c1f039 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
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -29,9 +30,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
@@ -237,8 +240,8 @@
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -254,20 +257,30 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardQuickAffordanceInteractor(
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 8d0c4ef..11fe905 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -26,9 +27,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
@@ -98,8 +101,8 @@
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -115,20 +118,30 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
featureFlags =
FakeFeatureFlags().apply {
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 32849cd..83a5d0e 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -27,9 +28,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -121,8 +124,8 @@
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val scope = CoroutineScope(IMMEDIATE)
- val selectionManager =
- KeyguardQuickAffordanceSelectionManager(
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
userFileManager =
mock<UserFileManager>().apply {
@@ -138,17 +141,26 @@
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
scope = scope,
- selectionManager = selectionManager,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
scope = scope,
backgroundDispatcher = IMMEDIATE,
secureSettings = FakeSettings(),
- selectionsManager = selectionManager,
+ selectionsManager = localUserSelectionManager,
),
configs =
setOf(
@@ -157,6 +169,7 @@
qrCodeScannerAffordanceConfig,
),
dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardBottomAreaViewModel(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..d85dd2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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 com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+
+class FakeKeyguardQuickAffordanceProviderClientFactory(
+ private val userTracker: UserTracker,
+ private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = {
+ FakeKeyguardQuickAffordanceProviderClient()
+ },
+) : KeyguardQuickAffordanceProviderClientFactory {
+
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return callback(userTracker.userId)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index a7eadba..0dd1fc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -66,7 +66,8 @@
_userId = _userInfo.id
_userHandle = UserHandle.of(_userId)
- callbacks.forEach { it.onUserChanged(_userId, userContext) }
+ val copy = callbacks.toList()
+ copy.forEach { it.onUserChanged(_userId, userContext) }
}
fun onProfileChanged() {