Merge changes Ie810d37d,I6947e1f7 into tm-qpr-dev am: 33b100711a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20295040

Change-Id: Iab695c0560de98afea3a2f415c50d655ece6beac
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Ale Nijamkin
2022-11-03 13:26:51 +00:00
committed by Automerger Merge Worker
21 changed files with 1165 additions and 52 deletions

View File

@@ -0,0 +1,27 @@
/*
* 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.shared.keyguard.shared.model
/**
* Collection of all supported "slots", placements where keyguard quick affordances can appear on
* the lock screen.
*/
object KeyguardQuickAffordanceSlots {
const val SLOT_ID_BOTTOM_START = "bottom_start"
const val SLOT_ID_BOTTOM_END = "bottom_end"
}

View File

@@ -125,6 +125,14 @@ object Flags {
// TODO(b/255607168): Tracking Bug
@JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213)
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
* Note that this flag does not enable individual implementations of quick affordances like the
* new camera quick affordance. Look for individual flags for those.
*/
@JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(214, teamfood = false)
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)

View File

@@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -71,6 +72,7 @@ import dagger.Provides;
KeyguardUserSwitcherComponent.class},
includes = {
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
StartKeyguardTransitionModule.class,

View File

@@ -53,6 +53,10 @@ constructor(
override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {

View File

@@ -0,0 +1,39 @@
/*
* 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 dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
@Module
object KeyguardDataQuickAffordanceModule {
@Provides
@ElementsIntoSet
fun quickAffordanceConfigs(
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
home,
quickAccessWallet,
qrCodeScanner,
)
}
}

View File

@@ -29,6 +29,10 @@ interface KeyguardQuickAffordanceConfig {
/** Unique identifier for this quick affordance. It must be globally unique. */
val key: String
val pickerName: String
val pickerIconResourceId: Int
/**
* The ever-changing state of the affordance.
*

View File

@@ -0,0 +1,62 @@
/*
* 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.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
@SysUISingleton
class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
// TODO(b/254858695): implement a persistence layer (database).
private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
suspend fun getSelections(): Map<String, List<String>> {
return _selections.value
}
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
suspend fun setSelections(
slotId: String,
affordanceIds: List<String>,
) {
// Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
// when we set its value to the same instance of the original map, even if we change the
// map by updating the value of one of its keys.
val copy = _selections.value.toMutableMap()
copy[slotId] = affordanceIds
_selections.value = copy
}
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -34,11 +36,16 @@ import kotlinx.coroutines.flow.Flow
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
@Application context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
override val pickerName = context.getString(R.string.qr_code_scanner_title)
override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
@@ -40,12 +42,17 @@ import kotlinx.coroutines.flow.Flow
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
@Application context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
override val pickerName = context.getString(R.string.accessibility_wallet_button)
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =

View File

@@ -0,0 +1,128 @@
/*
* 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.repository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
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.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Abstracts access to application state related to keyguard quick affordances. */
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
@Application private val scope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectionManager: KeyguardQuickAffordanceSelectionManager,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
) {
/**
* 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) }
}
}
.stateIn(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = emptyMap(),
)
/**
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
/**
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
suspend fun getSelections(): Map<String, List<String>> {
return selectionManager.getSelections()
}
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
fun setSelections(
slotId: String,
affordanceIds: List<String>,
) {
scope.launch(backgroundDispatcher) {
selectionManager.setSelections(
slotId = slotId,
affordanceIds = affordanceIds,
)
}
}
/**
* Returns the list of representation objects for all known affordances, regardless of what is
* selected. This is useful for building experiences like the picker/selector or user settings
* so the user can see everything that can be selected in a menu.
*/
fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
return configs.map { config ->
KeyguardQuickAffordancePickerRepresentation(
id = config.key,
name = config.pickerName,
iconResourceId = config.pickerIconResourceId,
)
}
}
/**
* Returns the list of representation objects for all available slots on the keyguard. This is
* useful for building experiences like the picker/selector or user settings so the user can see
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
// TODO(b/256195304): source these from a config XML file.
return listOf(
KeyguardSlotPickerRepresentation(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
),
KeyguardSlotPickerRepresentation(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
),
)
}
}

View File

@@ -18,19 +18,30 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@SysUISingleton
@@ -43,7 +54,12 @@ constructor(
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
) {
private val isUsingRepository: Boolean
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
@@ -72,7 +88,19 @@ constructor(
configKey: String,
expandable: Expandable?,
) {
@Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
@Suppress("UNCHECKED_CAST")
val config =
if (isUsingRepository) {
val (slotId, decodedConfigKey) = configKey.decode()
repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
} else {
registry.get(configKey)
}
if (config == null) {
Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
return
}
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
@@ -84,28 +112,138 @@ constructor(
}
}
/**
* Selects an affordance with the given ID on the slot with the given ID.
*
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
suspend fun select(slotId: String, affordanceId: String): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
selections.removeAt(0)
}
}
selections.add(affordanceId)
repository
.get()
.setSelections(
slotId = slotId,
affordanceIds = selections,
)
return true
}
/**
* Unselects one or all affordances from the slot with the given ID.
*
* @param slotId The ID of the slot.
* @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
* from the slot.
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* the affordance was not on the slot to begin with).
*/
suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
if (slots.find { it.id == slotId } == null) {
return false
}
if (affordanceId.isNullOrEmpty()) {
return if (
repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
) {
false
} else {
repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
true
}
}
val selections =
repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
.setSelections(
slotId = slotId,
affordanceIds = selections,
)
true
} else {
false
}
}
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<String>> {
check(isUsingRepository)
val selections = repository.get().getSelections()
return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
}
}
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
val configs = registry.getAll(position)
return if (isUsingRepository) {
repository
.get()
.selections
.map { it[position.toSlotId()] ?: emptyList() }
.flatMapLatest { configs -> combinedConfigs(position, configs) }
} else {
combinedConfigs(position, registry.getAll(position))
}
}
private fun combinedConfigs(
position: KeyguardQuickAffordancePosition,
configs: List<KeyguardQuickAffordanceConfig>,
): Flow<KeyguardQuickAffordanceModel> {
if (configs.isEmpty()) {
return flowOf(KeyguardQuickAffordanceModel.Hidden)
}
return combine(
configs.map { config ->
// We emit an initial "Hidden" value to make sure that there's always an initial
// value and avoid subtle bugs where the downstream isn't receiving any values
// because one config implementation is not emitting an initial value. For example,
// see b/244296596.
// We emit an initial "Hidden" value to make sure that there's always an
// initial value and avoid subtle bugs where the downstream isn't receiving
// any values because one config implementation is not emitting an initial
// value. For example, see b/244296596.
config.lockScreenState.onStart {
emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
) { states ->
val index =
states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
states.indexOfFirst { state ->
state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
}
if (index != -1) {
val visibleState =
states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
val configKey = configs[index].key
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index].key,
configKey =
if (isUsingRepository) {
configKey.encode(position.toSlotId())
} else {
configKey
},
icon = visibleState.icon,
activationState = visibleState.activationState,
)
@@ -145,4 +283,39 @@ constructor(
)
}
}
private fun KeyguardQuickAffordancePosition.toSlotId(): String {
return when (this) {
KeyguardQuickAffordancePosition.BOTTOM_START ->
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
KeyguardQuickAffordancePosition.BOTTOM_END ->
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
}
}
private fun String.encode(slotId: String): String {
return "$slotId$DELIMITER$this"
}
private fun String.decode(): Pair<String, String> {
val splitUp = this.split(DELIMITER)
return Pair(splitUp[0], splitUp[1])
}
fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
check(isUsingRepository)
return repository.get().getAffordancePickerRepresentations()
}
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
check(isUsingRepository)
return repository.get().getSlotPickerRepresentations()
}
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.shared.model
import androidx.annotation.DrawableRes
/**
* Representation of a quick affordance for use to build "picker", "selector", or "settings"
* experiences.
*/
data class KeyguardQuickAffordancePickerRepresentation(
val id: String,
val name: String,
@DrawableRes val iconResourceId: Int,
)

View File

@@ -0,0 +1,28 @@
/*
* 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.shared.model
/**
* Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
* "settings" experiences.
*/
data class KeyguardSlotPickerRepresentation(
val id: String,
/** The maximum number of selected affordances that can be present on this slot. */
val maxSelectedAffordances: Int = 1,
)

View File

@@ -23,14 +23,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
/**
* Fake implementation of a quick affordance data source.
*
* This class is abstract to force tests to provide extensions of it as the system that references
* these configs uses each implementation's class type to refer to them.
*/
abstract class FakeKeyguardQuickAffordanceConfig(
/** Fake implementation of a quick affordance data source. */
class FakeKeyguardQuickAffordanceConfig(
override val key: String,
override val pickerName: String = key,
override val pickerIconResourceId: Int = 0,
) : KeyguardQuickAffordanceConfig {
var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled

View File

@@ -0,0 +1,127 @@
/*
* 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 androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
@Before
fun setUp() {
underTest = KeyguardQuickAffordanceSelectionManager()
}
@Test
fun setSelections() =
runBlocking(IMMEDIATE) {
var affordanceIdsBySlotId: Map<String, List<String>>? = null
val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
val slotId1 = "slot1"
val slotId2 = "slot2"
val affordanceId1 = "affordance1"
val affordanceId2 = "affordance2"
val affordanceId3 = "affordance3"
underTest.setSelections(
slotId = slotId1,
affordanceIds = listOf(affordanceId1),
)
assertSelections(
affordanceIdsBySlotId,
mapOf(
slotId1 to listOf(affordanceId1),
),
)
underTest.setSelections(
slotId = slotId2,
affordanceIds = listOf(affordanceId2),
)
assertSelections(
affordanceIdsBySlotId,
mapOf(
slotId1 to listOf(affordanceId1),
slotId2 to listOf(affordanceId2),
)
)
underTest.setSelections(
slotId = slotId1,
affordanceIds = listOf(affordanceId1, affordanceId3),
)
assertSelections(
affordanceIdsBySlotId,
mapOf(
slotId1 to listOf(affordanceId1, affordanceId3),
slotId2 to listOf(affordanceId2),
)
)
underTest.setSelections(
slotId = slotId1,
affordanceIds = listOf(affordanceId3),
)
assertSelections(
affordanceIdsBySlotId,
mapOf(
slotId1 to listOf(affordanceId3),
slotId2 to listOf(affordanceId2),
)
)
underTest.setSelections(
slotId = slotId2,
affordanceIds = listOf(),
)
assertSelections(
affordanceIdsBySlotId,
mapOf(
slotId1 to listOf(affordanceId3),
slotId2 to listOf(),
)
)
job.cancel()
}
private suspend fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
) {
assertThat(underTest.getSelections()).isEqualTo(expected)
assertThat(observed).isEqualTo(expected)
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
}

View File

@@ -50,7 +50,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
}
@Test

View File

@@ -59,6 +59,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
mock(),
walletController,
activityStarter,
)

View File

@@ -0,0 +1,152 @@
/*
* 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.repository
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
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.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardQuickAffordanceRepository
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
@Before
fun setUp() {
config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
underTest =
KeyguardQuickAffordanceRepository(
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
configs = setOf(config1, config2),
)
}
@Test
fun setSelections() =
runBlocking(IMMEDIATE) {
var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
val slotId1 = "slot1"
val slotId2 = "slot2"
underTest.setSelections(slotId1, listOf(config1.key))
assertSelections(
configsBySlotId,
mapOf(
slotId1 to listOf(config1),
),
)
underTest.setSelections(slotId2, listOf(config2.key))
assertSelections(
configsBySlotId,
mapOf(
slotId1 to listOf(config1),
slotId2 to listOf(config2),
),
)
underTest.setSelections(slotId1, emptyList())
underTest.setSelections(slotId2, listOf(config1.key))
assertSelections(
configsBySlotId,
mapOf(
slotId1 to emptyList(),
slotId2 to listOf(config1),
),
)
job.cancel()
}
@Test
fun getAffordancePickerRepresentations() {
assertThat(underTest.getAffordancePickerRepresentations())
.isEqualTo(
listOf(
KeyguardQuickAffordancePickerRepresentation(
id = config1.key,
name = config1.pickerName,
iconResourceId = config1.pickerIconResourceId,
),
KeyguardQuickAffordancePickerRepresentation(
id = config2.key,
name = config2.pickerName,
iconResourceId = config2.pickerIconResourceId,
),
)
)
}
@Test
fun getSlotPickerRepresentations() {
assertThat(underTest.getSlotPickerRepresentations())
.isEqualTo(
listOf(
KeyguardSlotPickerRepresentation(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
maxSelectedAffordances = 1,
),
KeyguardSlotPickerRepresentation(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
maxSelectedAffordances = 1,
),
)
)
}
private suspend fun assertSelections(
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
assertThat(observed).isEqualTo(expected)
assertThat(underTest.getSelections())
.isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
expected.forEach { (slotId, configs) ->
assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
}
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
}

View File

@@ -25,10 +25,14 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.flags.FakeFeatureFlags
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.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -189,6 +195,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
/* startActivity= */ true,
),
)
private val IMMEDIATE = Dispatchers.Main.immediate
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
val quickAccessWallet =
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
)
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {},
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {},
quickAccessWallet,
qrCodeScanner,
),
),
),
@@ -244,6 +256,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
featureFlags =
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
},
repository = { quickAffordanceRepository },
)
}

View File

@@ -22,22 +22,31 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.flags.FakeFeatureFlags
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.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.yield
import org.junit.Before
@@ -47,6 +56,7 @@ import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -71,20 +82,25 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
repository.setKeyguardShowing(true)
homeControls =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWallet =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {}
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
)
qrCodeScanner =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {}
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
featureFlags =
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
)
}
@@ -210,6 +228,270 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun select() =
runBlocking(IMMEDIATE) {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
qrCodeScanner.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf<String, List<String>>(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
var startConfig: KeyguardQuickAffordanceModel? = null
val job1 =
underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { startConfig = it }
.launchIn(this)
var endConfig: KeyguardQuickAffordanceModel? = null
val job2 =
underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
.onEach { endConfig = it }
.launchIn(this)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
yield()
yield()
assertThat(startConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
"::${homeControls.key}",
icon = ICON,
activationState = ActivationState.NotSupported,
)
)
assertThat(endConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
listOf(homeControls.key),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
underTest.select(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
quickAccessWallet.key
)
yield()
yield()
assertThat(startConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
"::${quickAccessWallet.key}",
icon = ICON,
activationState = ActivationState.NotSupported,
)
)
assertThat(endConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
listOf(quickAccessWallet.key),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
yield()
yield()
assertThat(startConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
"::${quickAccessWallet.key}",
icon = ICON,
activationState = ActivationState.NotSupported,
)
)
assertThat(endConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
"::${qrCodeScanner.key}",
icon = ICON,
activationState = ActivationState.NotSupported,
)
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
listOf(quickAccessWallet.key),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
listOf(qrCodeScanner.key),
)
)
job1.cancel()
job2.cancel()
}
@Test
fun `unselect - one`() =
runBlocking(IMMEDIATE) {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
qrCodeScanner.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
var startConfig: KeyguardQuickAffordanceModel? = null
val job1 =
underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { startConfig = it }
.launchIn(this)
var endConfig: KeyguardQuickAffordanceModel? = null
val job2 =
underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
.onEach { endConfig = it }
.launchIn(this)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
yield()
yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
yield()
yield()
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
yield()
yield()
assertThat(startConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
assertThat(endConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
"::${quickAccessWallet.key}",
icon = ICON,
activationState = ActivationState.NotSupported,
)
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
listOf(quickAccessWallet.key),
)
)
underTest.unselect(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
quickAccessWallet.key
)
yield()
yield()
assertThat(startConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
assertThat(endConfig)
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf<String, List<String>>(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
job1.cancel()
job2.cancel()
}
@Test
fun `unselect - all`() =
runBlocking(IMMEDIATE) {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
qrCodeScanner.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
yield()
yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
yield()
yield()
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
yield()
yield()
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
listOf(quickAccessWallet.key),
)
)
underTest.unselect(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
null,
)
yield()
yield()
assertThat(underTest.getSelections())
.isEqualTo(
mapOf<String, List<String>>(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
}
companion object {
private val ICON: Icon = mock {
whenever(this.contentDescription)
@@ -220,5 +502,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
private val IMMEDIATE = Dispatchers.Main.immediate
}
}

View File

@@ -23,10 +23,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
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.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -41,6 +45,8 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
.thenReturn(RETURNED_BURN_IN_OFFSET)
homeControlsQuickAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
) {}
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWalletAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
) {}
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
)
qrCodeScannerAffordanceConfig =
object :
FakeKeyguardQuickAffordanceConfig(
BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
) {}
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -116,6 +115,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
scope = CoroutineScope(IMMEDIATE),
backgroundDispatcher = IMMEDIATE,
selectionManager = KeyguardQuickAffordanceSelectionManager(),
configs =
setOf(
homeControlsQuickAffordanceConfig,
quickAccessWalletAffordanceConfig,
qrCodeScannerAffordanceConfig,
),
)
underTest =
KeyguardBottomAreaViewModel(
keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
featureFlags =
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
},
repository = { quickAffordanceRepository },
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -576,5 +592,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
private val IMMEDIATE = Dispatchers.Main.immediate
}
}