Merge "Multi-user customizable quick affordances." into tm-qpr-dev
This commit is contained in:
@@ -17,12 +17,19 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.quickaffordance
|
package com.android.systemui.keyguard.data.quickaffordance
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.ElementsIntoSet
|
import dagger.multibindings.ElementsIntoSet
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
object KeyguardDataQuickAffordanceModule {
|
interface KeyguardDataQuickAffordanceModule {
|
||||||
|
@Binds
|
||||||
|
fun providerClientFactory(
|
||||||
|
impl: KeyguardQuickAffordanceProviderClientFactoryImpl,
|
||||||
|
): KeyguardQuickAffordanceProviderClientFactory
|
||||||
|
|
||||||
|
companion object {
|
||||||
@Provides
|
@Provides
|
||||||
@ElementsIntoSet
|
@ElementsIntoSet
|
||||||
fun quickAffordanceConfigs(
|
fun quickAffordanceConfigs(
|
||||||
@@ -41,3 +48,4 @@ object KeyguardDataQuickAffordanceModule {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ constructor(
|
|||||||
@Application private val scope: CoroutineScope,
|
@Application private val scope: CoroutineScope,
|
||||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||||
private val secureSettings: SecureSettings,
|
private val secureSettings: SecureSettings,
|
||||||
private val selectionsManager: KeyguardQuickAffordanceSelectionManager,
|
private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val BINDINGS =
|
private val BINDINGS =
|
||||||
|
|||||||
@@ -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 = ","
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,153 +17,22 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.quickaffordance
|
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.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
|
* Defines interface for classes that manage and provide access to the current "selections" of
|
||||||
* the question "which affordances should the keyguard show?".
|
* keyguard quick affordances, answering the question "which affordances should the keyguard show?".
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
interface KeyguardQuickAffordanceSelectionManager {
|
||||||
@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,
|
|
||||||
)
|
|
||||||
|
|
||||||
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
|
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
|
||||||
val selections: Flow<Map<String, List<String>>> =
|
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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
|
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
|
||||||
* descending priority order.
|
* descending priority order.
|
||||||
*/
|
*/
|
||||||
fun getSelections(): Map<String, List<String>> {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
|
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
|
||||||
@@ -172,25 +41,9 @@ constructor(
|
|||||||
fun setSelections(
|
fun setSelections(
|
||||||
slotId: String,
|
slotId: String,
|
||||||
affordanceIds: List<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 {
|
companion object {
|
||||||
private const val TAG = "KeyguardQuickAffordanceSelectionManager"
|
|
||||||
const val FILE_NAME = "quick_affordance_selections"
|
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 = ","
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,47 +18,95 @@
|
|||||||
package com.android.systemui.keyguard.data.repository
|
package com.android.systemui.keyguard.data.repository
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.UserHandle
|
||||||
import com.android.systemui.Dumpable
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.R
|
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.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
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.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
|
||||||
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
|
||||||
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
|
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
|
||||||
|
import com.android.systemui.settings.UserTracker
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/** Abstracts access to application state related to keyguard quick affordances. */
|
/** Abstracts access to application state related to keyguard quick affordances. */
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class KeyguardQuickAffordanceRepository
|
class KeyguardQuickAffordanceRepository
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Application private val appContext: Context,
|
@Application private val appContext: Context,
|
||||||
@Application private val scope: CoroutineScope,
|
@Application private val scope: CoroutineScope,
|
||||||
private val selectionManager: KeyguardQuickAffordanceSelectionManager,
|
private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
|
||||||
|
private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
|
||||||
|
private val userTracker: UserTracker,
|
||||||
legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
|
legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
|
||||||
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
|
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
|
||||||
dumpManager: DumpManager,
|
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
|
* List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
|
||||||
* given ID. The configs are sorted in descending priority order.
|
* given ID. The configs are sorted in descending priority order.
|
||||||
*/
|
*/
|
||||||
val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
|
val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
|
||||||
selectionManager.selections
|
selectionManager
|
||||||
.map { selectionsBySlotId ->
|
.flatMapLatest { selectionManager ->
|
||||||
|
selectionManager.selections.map { selectionsBySlotId ->
|
||||||
selectionsBySlotId.mapValues { (_, selections) ->
|
selectionsBySlotId.mapValues { (_, selections) ->
|
||||||
configs.filter { selections.contains(it.key) }
|
configs.filter { selections.contains(it.key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
@@ -99,7 +147,7 @@ constructor(
|
|||||||
* slot with the given ID. The configs are sorted in descending priority order.
|
* slot with the given ID. The configs are sorted in descending priority order.
|
||||||
*/
|
*/
|
||||||
fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
|
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) }
|
return configs.filter { selections.contains(it.key) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +156,7 @@ constructor(
|
|||||||
* are sorted in descending priority order.
|
* are sorted in descending priority order.
|
||||||
*/
|
*/
|
||||||
fun getSelections(): Map<String, List<String>> {
|
fun getSelections(): Map<String, List<String>> {
|
||||||
return selectionManager.getSelections()
|
return selectionManager.value.getSelections()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,7 +167,7 @@ constructor(
|
|||||||
slotId: String,
|
slotId: String,
|
||||||
affordanceIds: List<String>,
|
affordanceIds: List<String>,
|
||||||
) {
|
) {
|
||||||
selectionManager.setSelections(
|
selectionManager.value.setSelections(
|
||||||
slotId = slotId,
|
slotId = slotId,
|
||||||
affordanceIds = affordanceIds,
|
affordanceIds = affordanceIds,
|
||||||
)
|
)
|
||||||
@@ -188,6 +236,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "KeyguardQuickAffordanceRepository"
|
||||||
private const val SLOT_CONFIG_DELIMITER = ":"
|
private const val SLOT_CONFIG_DELIMITER = ":"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,8 +190,6 @@ constructor(
|
|||||||
|
|
||||||
/** Returns affordance IDs indexed by slot ID, for all known slots. */
|
/** Returns affordance IDs indexed by slot ID, for all known slots. */
|
||||||
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
|
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
|
||||||
check(isUsingRepository)
|
|
||||||
|
|
||||||
val slots = repository.get().getSlotPickerRepresentations()
|
val slots = repository.get().getSlotPickerRepresentations()
|
||||||
val selections = repository.get().getSelections()
|
val selections = repository.get().getSelections()
|
||||||
val affordanceById =
|
val affordanceById =
|
||||||
@@ -312,8 +310,6 @@ constructor(
|
|||||||
|
|
||||||
suspend fun getAffordancePickerRepresentations():
|
suspend fun getAffordancePickerRepresentations():
|
||||||
List<KeyguardQuickAffordancePickerRepresentation> {
|
List<KeyguardQuickAffordancePickerRepresentation> {
|
||||||
check(isUsingRepository)
|
|
||||||
|
|
||||||
return repository.get().getAffordancePickerRepresentations()
|
return repository.get().getAffordancePickerRepresentations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.user;
|
package com.android.systemui.user;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
|
||||||
import com.android.settingslib.users.EditUserInfoController;
|
import com.android.settingslib.users.EditUserInfoController;
|
||||||
import com.android.systemui.user.data.repository.UserRepositoryModule;
|
import com.android.systemui.user.data.repository.UserRepositoryModule;
|
||||||
@@ -51,4 +52,22 @@ public abstract class UserModule {
|
|||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(UserSwitcherActivity.class)
|
@ClassKey(UserSwitcherActivity.class)
|
||||||
public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard
|
|||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ProviderInfo
|
import android.content.pm.ProviderInfo
|
||||||
|
import android.os.UserHandle
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
import com.android.systemui.SystemUIAppComponentFactoryBase
|
import com.android.systemui.SystemUIAppComponentFactoryBase
|
||||||
@@ -27,8 +28,10 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.flags.FakeFeatureFlags
|
import com.android.systemui.flags.FakeFeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
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.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.FakeKeyguardRepository
|
||||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||||
@@ -74,8 +77,8 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
|||||||
|
|
||||||
underTest = KeyguardQuickAffordanceProvider()
|
underTest = KeyguardQuickAffordanceProvider()
|
||||||
val scope = CoroutineScope(IMMEDIATE)
|
val scope = CoroutineScope(IMMEDIATE)
|
||||||
val selectionManager =
|
val localUserSelectionManager =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock<UserFileManager>().apply {
|
mock<UserFileManager>().apply {
|
||||||
@@ -91,11 +94,20 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
|||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||||
)
|
)
|
||||||
|
val remoteUserSelectionManager =
|
||||||
|
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||||
|
scope = scope,
|
||||||
|
userTracker = userTracker,
|
||||||
|
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
|
)
|
||||||
val quickAffordanceRepository =
|
val quickAffordanceRepository =
|
||||||
KeyguardQuickAffordanceRepository(
|
KeyguardQuickAffordanceRepository(
|
||||||
appContext = context,
|
appContext = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
selectionManager = selectionManager,
|
localUserSelectionManager = localUserSelectionManager,
|
||||||
|
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||||
|
userTracker = userTracker,
|
||||||
configs =
|
configs =
|
||||||
setOf(
|
setOf(
|
||||||
FakeKeyguardQuickAffordanceConfig(
|
FakeKeyguardQuickAffordanceConfig(
|
||||||
@@ -114,9 +126,10 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
|||||||
scope = scope,
|
scope = scope,
|
||||||
backgroundDispatcher = IMMEDIATE,
|
backgroundDispatcher = IMMEDIATE,
|
||||||
secureSettings = FakeSettings(),
|
secureSettings = FakeSettings(),
|
||||||
selectionsManager = selectionManager,
|
selectionsManager = localUserSelectionManager,
|
||||||
),
|
),
|
||||||
dumpManager = mock(),
|
dumpManager = mock(),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
)
|
)
|
||||||
underTest.interactor =
|
underTest.interactor =
|
||||||
KeyguardQuickAffordanceInteractor(
|
KeyguardQuickAffordanceInteractor(
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var testScope: TestScope
|
private lateinit var testScope: TestScope
|
||||||
private lateinit var testDispatcher: TestDispatcher
|
private lateinit var testDispatcher: TestDispatcher
|
||||||
private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager
|
private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
|
||||||
private lateinit var settings: FakeSettings
|
private lateinit var settings: FakeSettings
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -75,7 +75,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
|
|||||||
testDispatcher = UnconfinedTestDispatcher()
|
testDispatcher = UnconfinedTestDispatcher()
|
||||||
testScope = TestScope(testDispatcher)
|
testScope = TestScope(testDispatcher)
|
||||||
selectionManager =
|
selectionManager =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock {
|
mock {
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ import org.mockito.MockitoAnnotations
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
|
class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
|
||||||
|
|
||||||
@Mock private lateinit var userFileManager: UserFileManager
|
@Mock private lateinit var userFileManager: UserFileManager
|
||||||
|
|
||||||
private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
|
private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager
|
||||||
|
|
||||||
private lateinit var userTracker: FakeUserTracker
|
private lateinit var userTracker: FakeUserTracker
|
||||||
private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
|
private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
|
||||||
@@ -74,7 +74,7 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
|
|||||||
Dispatchers.setMain(dispatcher)
|
Dispatchers.setMain(dispatcher)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager = userFileManager,
|
userFileManager = userFileManager,
|
||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,23 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.repository
|
package com.android.systemui.keyguard.data.repository
|
||||||
|
|
||||||
|
import android.content.pm.UserInfo
|
||||||
|
import android.os.UserHandle
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
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.KeyguardQuickAffordanceConfig
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
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.KeyguardQuickAffordancePickerRepresentation
|
||||||
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
|
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
|
||||||
import com.android.systemui.settings.FakeUserTracker
|
import com.android.systemui.settings.FakeUserTracker
|
||||||
import com.android.systemui.settings.UserFileManager
|
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.FakeSharedPreferences
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
@@ -39,6 +45,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -55,14 +62,24 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
|
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
|
||||||
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
|
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
|
||||||
|
private lateinit var userTracker: FakeUserTracker
|
||||||
|
private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
|
||||||
|
private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
|
config1 =
|
||||||
config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
|
FakeKeyguardQuickAffordanceConfig(
|
||||||
|
FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1
|
||||||
|
)
|
||||||
|
config2 =
|
||||||
|
FakeKeyguardQuickAffordanceConfig(
|
||||||
|
FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2
|
||||||
|
)
|
||||||
val scope = CoroutineScope(IMMEDIATE)
|
val scope = CoroutineScope(IMMEDIATE)
|
||||||
val selectionManager =
|
userTracker = FakeUserTracker()
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
val localUserSelectionManager =
|
||||||
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock<UserFileManager>().apply {
|
mock<UserFileManager>().apply {
|
||||||
@@ -75,24 +92,45 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
.thenReturn(FakeSharedPreferences())
|
.thenReturn(FakeSharedPreferences())
|
||||||
},
|
},
|
||||||
userTracker = FakeUserTracker(),
|
userTracker = userTracker,
|
||||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
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 =
|
underTest =
|
||||||
KeyguardQuickAffordanceRepository(
|
KeyguardQuickAffordanceRepository(
|
||||||
appContext = context,
|
appContext = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
selectionManager = selectionManager,
|
localUserSelectionManager = localUserSelectionManager,
|
||||||
|
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||||
|
userTracker = userTracker,
|
||||||
legacySettingSyncer =
|
legacySettingSyncer =
|
||||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
backgroundDispatcher = IMMEDIATE,
|
backgroundDispatcher = IMMEDIATE,
|
||||||
secureSettings = FakeSettings(),
|
secureSettings = FakeSettings(),
|
||||||
selectionsManager = selectionManager,
|
selectionsManager = localUserSelectionManager,
|
||||||
),
|
),
|
||||||
configs = setOf(config1, config2),
|
configs = setOf(config1, config2),
|
||||||
dumpManager = mock(),
|
dumpManager = mock(),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +225,53 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>?,
|
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
|
||||||
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
|
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
|
||||||
) {
|
) {
|
||||||
@@ -201,5 +285,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package com.android.systemui.keyguard.domain.interactor
|
package com.android.systemui.keyguard.domain.interactor
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.UserHandle
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -29,9 +30,11 @@ import com.android.systemui.flags.FakeFeatureFlags
|
|||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
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.KeyguardQuickAffordanceConfig
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
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.FakeKeyguardRepository
|
||||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||||
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
|
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
|
||||||
@@ -237,8 +240,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
|
|||||||
val qrCodeScanner =
|
val qrCodeScanner =
|
||||||
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
||||||
val scope = CoroutineScope(IMMEDIATE)
|
val scope = CoroutineScope(IMMEDIATE)
|
||||||
val selectionManager =
|
val localUserSelectionManager =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock<UserFileManager>().apply {
|
mock<UserFileManager>().apply {
|
||||||
@@ -254,20 +257,30 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
|
|||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||||
)
|
)
|
||||||
|
val remoteUserSelectionManager =
|
||||||
|
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||||
|
scope = scope,
|
||||||
|
userTracker = userTracker,
|
||||||
|
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
|
)
|
||||||
val quickAffordanceRepository =
|
val quickAffordanceRepository =
|
||||||
KeyguardQuickAffordanceRepository(
|
KeyguardQuickAffordanceRepository(
|
||||||
appContext = context,
|
appContext = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
selectionManager = selectionManager,
|
localUserSelectionManager = localUserSelectionManager,
|
||||||
|
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||||
|
userTracker = userTracker,
|
||||||
legacySettingSyncer =
|
legacySettingSyncer =
|
||||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
backgroundDispatcher = IMMEDIATE,
|
backgroundDispatcher = IMMEDIATE,
|
||||||
secureSettings = FakeSettings(),
|
secureSettings = FakeSettings(),
|
||||||
selectionsManager = selectionManager,
|
selectionsManager = localUserSelectionManager,
|
||||||
),
|
),
|
||||||
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
|
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
|
||||||
dumpManager = mock(),
|
dumpManager = mock(),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
)
|
)
|
||||||
underTest =
|
underTest =
|
||||||
KeyguardQuickAffordanceInteractor(
|
KeyguardQuickAffordanceInteractor(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.domain.interactor
|
package com.android.systemui.keyguard.domain.interactor
|
||||||
|
|
||||||
|
import android.os.UserHandle
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -26,9 +27,11 @@ import com.android.systemui.flags.FakeFeatureFlags
|
|||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
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.KeyguardQuickAffordanceConfig
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
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.FakeKeyguardRepository
|
||||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||||
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
|
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
|
||||||
@@ -98,8 +101,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
|||||||
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
||||||
val scope = CoroutineScope(IMMEDIATE)
|
val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
|
||||||
val selectionManager =
|
val localUserSelectionManager =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock<UserFileManager>().apply {
|
mock<UserFileManager>().apply {
|
||||||
@@ -115,20 +118,30 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
|||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||||
)
|
)
|
||||||
|
val remoteUserSelectionManager =
|
||||||
|
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||||
|
scope = scope,
|
||||||
|
userTracker = userTracker,
|
||||||
|
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
|
)
|
||||||
val quickAffordanceRepository =
|
val quickAffordanceRepository =
|
||||||
KeyguardQuickAffordanceRepository(
|
KeyguardQuickAffordanceRepository(
|
||||||
appContext = context,
|
appContext = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
selectionManager = selectionManager,
|
localUserSelectionManager = localUserSelectionManager,
|
||||||
|
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||||
|
userTracker = userTracker,
|
||||||
legacySettingSyncer =
|
legacySettingSyncer =
|
||||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
backgroundDispatcher = IMMEDIATE,
|
backgroundDispatcher = IMMEDIATE,
|
||||||
secureSettings = FakeSettings(),
|
secureSettings = FakeSettings(),
|
||||||
selectionsManager = selectionManager,
|
selectionsManager = localUserSelectionManager,
|
||||||
),
|
),
|
||||||
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
|
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
|
||||||
dumpManager = mock(),
|
dumpManager = mock(),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
)
|
)
|
||||||
featureFlags =
|
featureFlags =
|
||||||
FakeFeatureFlags().apply {
|
FakeFeatureFlags().apply {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.keyguard.ui.viewmodel
|
package com.android.systemui.keyguard.ui.viewmodel
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.UserHandle
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -27,9 +28,11 @@ import com.android.systemui.flags.FakeFeatureFlags
|
|||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
|
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.KeyguardQuickAffordanceConfig
|
||||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
|
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.FakeKeyguardRepository
|
||||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
|
||||||
@@ -121,8 +124,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
|||||||
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
|
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
|
||||||
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
|
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
|
||||||
val scope = CoroutineScope(IMMEDIATE)
|
val scope = CoroutineScope(IMMEDIATE)
|
||||||
val selectionManager =
|
val localUserSelectionManager =
|
||||||
KeyguardQuickAffordanceSelectionManager(
|
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||||
context = context,
|
context = context,
|
||||||
userFileManager =
|
userFileManager =
|
||||||
mock<UserFileManager>().apply {
|
mock<UserFileManager>().apply {
|
||||||
@@ -138,17 +141,26 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
|||||||
userTracker = userTracker,
|
userTracker = userTracker,
|
||||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||||
)
|
)
|
||||||
|
val remoteUserSelectionManager =
|
||||||
|
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||||
|
scope = scope,
|
||||||
|
userTracker = userTracker,
|
||||||
|
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
|
)
|
||||||
val quickAffordanceRepository =
|
val quickAffordanceRepository =
|
||||||
KeyguardQuickAffordanceRepository(
|
KeyguardQuickAffordanceRepository(
|
||||||
appContext = context,
|
appContext = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
selectionManager = selectionManager,
|
localUserSelectionManager = localUserSelectionManager,
|
||||||
|
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||||
|
userTracker = userTracker,
|
||||||
legacySettingSyncer =
|
legacySettingSyncer =
|
||||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
backgroundDispatcher = IMMEDIATE,
|
backgroundDispatcher = IMMEDIATE,
|
||||||
secureSettings = FakeSettings(),
|
secureSettings = FakeSettings(),
|
||||||
selectionsManager = selectionManager,
|
selectionsManager = localUserSelectionManager,
|
||||||
),
|
),
|
||||||
configs =
|
configs =
|
||||||
setOf(
|
setOf(
|
||||||
@@ -157,6 +169,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
|||||||
qrCodeScannerAffordanceConfig,
|
qrCodeScannerAffordanceConfig,
|
||||||
),
|
),
|
||||||
dumpManager = mock(),
|
dumpManager = mock(),
|
||||||
|
userHandle = UserHandle.SYSTEM,
|
||||||
)
|
)
|
||||||
underTest =
|
underTest =
|
||||||
KeyguardBottomAreaViewModel(
|
KeyguardBottomAreaViewModel(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,8 @@ class FakeUserTracker(
|
|||||||
_userId = _userInfo.id
|
_userId = _userInfo.id
|
||||||
_userHandle = UserHandle.of(_userId)
|
_userHandle = UserHandle.of(_userId)
|
||||||
|
|
||||||
callbacks.forEach { it.onUserChanged(_userId, userContext) }
|
val copy = callbacks.toList()
|
||||||
|
copy.forEach { it.onUserChanged(_userId, userContext) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onProfileChanged() {
|
fun onProfileChanged() {
|
||||||
|
|||||||
Reference in New Issue
Block a user