Loading packages/SystemUI/docs/device-entry/quickaffordance.md +5 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,11 @@ A picker experience may: * Unselect an already-selected quick affordance from a slot * Unselect an already-selected quick affordance from a slot * Unselect all already-selected quick affordances from a slot * Unselect all already-selected quick affordances from a slot ## Device Policy Returning `DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL` or `DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL` from `DevicePolicyManager#getKeyguardDisabledFeatures` will disable the keyguard quick affordance feature on the device. ## Testing ## Testing * Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` * Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` * Manually verify that your implementation works in multi-user environments from both the main user and a secondary user * Manually verify that your implementation works in multi-user environments from both the main user and a secondary user Loading packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +6 −6 Original line number Original line Diff line number Diff line Loading @@ -131,7 +131,7 @@ class CustomizationProvider : throw UnsupportedOperationException() throw UnsupportedOperationException() } } return insertSelection(values) return runBlocking { insertSelection(values) } } } override fun query( override fun query( Loading Loading @@ -171,7 +171,7 @@ class CustomizationProvider : throw UnsupportedOperationException() throw UnsupportedOperationException() } } return deleteSelection(uri, selectionArgs) return runBlocking { deleteSelection(uri, selectionArgs) } } } override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { Loading @@ -189,7 +189,7 @@ class CustomizationProvider : } } } } private fun insertSelection(values: ContentValues?): Uri? { private suspend fun insertSelection(values: ContentValues?): Uri? { if (values == null) { if (values == null) { throw IllegalArgumentException("Cannot insert selection, no values passed in!") throw IllegalArgumentException("Cannot insert selection, no values passed in!") } } Loading Loading @@ -311,7 +311,7 @@ class CustomizationProvider : } } } } private fun querySlots(): Cursor { private suspend fun querySlots(): Cursor { return MatrixCursor( return MatrixCursor( arrayOf( arrayOf( Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, Loading @@ -330,7 +330,7 @@ class CustomizationProvider : } } } } private fun queryFlags(): Cursor { private suspend fun queryFlags(): Cursor { return MatrixCursor( return MatrixCursor( arrayOf( arrayOf( Contract.FlagsTable.Columns.NAME, Contract.FlagsTable.Columns.NAME, Loading @@ -353,7 +353,7 @@ class CustomizationProvider : } } } } private fun deleteSelection( private suspend fun deleteSelection( uri: Uri, uri: Uri, selectionArgs: Array<out String>?, selectionArgs: Array<out String>?, ): Int { ): Int { Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +54 −8 Original line number Original line Diff line number Diff line Loading @@ -18,12 +18,14 @@ package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor import android.app.AlertDialog import android.app.AlertDialog import android.app.admin.DevicePolicyManager import android.content.Intent import android.content.Intent import android.util.Log import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig Loading @@ -41,13 +43,17 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import dagger.Lazy import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @SysUISingleton class KeyguardQuickAffordanceInteractor class KeyguardQuickAffordanceInteractor @Inject @Inject Loading @@ -61,6 +67,8 @@ constructor( private val featureFlags: FeatureFlags, private val featureFlags: FeatureFlags, private val repository: Lazy<KeyguardQuickAffordanceRepository>, private val repository: Lazy<KeyguardQuickAffordanceRepository>, private val launchAnimator: DialogLaunchAnimator, private val launchAnimator: DialogLaunchAnimator, private val devicePolicyManager: DevicePolicyManager, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { ) { private val isUsingRepository: Boolean private val isUsingRepository: Boolean get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) Loading @@ -74,9 +82,13 @@ constructor( get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) /** Returns an observable for the quick affordance at the given position. */ /** Returns an observable for the quick affordance at the given position. */ fun quickAffordance( suspend fun quickAffordance( position: KeyguardQuickAffordancePosition position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceModel> { ): Flow<KeyguardQuickAffordanceModel> { if (isFeatureDisabledByDevicePolicy()) { return flowOf(KeyguardQuickAffordanceModel.Hidden) } return combine( return combine( quickAffordanceAlwaysVisible(position), quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, keyguardInteractor.isDozing, Loading Loading @@ -148,13 +160,20 @@ constructor( * * * @return `true` if the affordance was selected successfully; `false` otherwise. * @return `true` if the affordance was selected successfully; `false` otherwise. */ */ fun select(slotId: String, affordanceId: String): Boolean { suspend fun select(slotId: String, affordanceId: String): Boolean { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return false } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() val slot = slots.find { it.id == slotId } ?: return false val slot = slots.find { it.id == slotId } ?: return false val selections = val selections = repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() repository .get() .getCurrentSelections() .getOrDefault(slotId, emptyList()) .toMutableList() val alreadySelected = selections.remove(affordanceId) val alreadySelected = selections.remove(affordanceId) if (!alreadySelected) { if (!alreadySelected) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { Loading Loading @@ -183,8 +202,11 @@ constructor( * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if * the affordance was not on the slot to begin with). * the affordance was not on the slot to begin with). */ */ fun unselect(slotId: String, affordanceId: String?): Boolean { suspend fun unselect(slotId: String, affordanceId: String?): Boolean { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return false } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() if (slots.find { it.id == slotId } == null) { if (slots.find { it.id == slotId } == null) { Loading @@ -203,7 +225,11 @@ constructor( } } val selections = val selections = repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() repository .get() .getCurrentSelections() .getOrDefault(slotId, emptyList()) .toMutableList() return if (selections.remove(affordanceId)) { return if (selections.remove(affordanceId)) { repository repository .get() .get() Loading @@ -219,6 +245,10 @@ 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>> { if (isFeatureDisabledByDevicePolicy()) { return emptyMap() } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() val selections = repository.get().getCurrentSelections() val selections = repository.get().getCurrentSelections() val affordanceById = val affordanceById = Loading Loading @@ -343,13 +373,17 @@ constructor( return repository.get().getAffordancePickerRepresentations() return repository.get().getAffordancePickerRepresentations() } } fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return emptyList() } return repository.get().getSlotPickerRepresentations() return repository.get().getSlotPickerRepresentations() } } fun getPickerFlags(): List<KeyguardPickerFlag> { suspend fun getPickerFlags(): List<KeyguardPickerFlag> { return listOf( return listOf( KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, Loading @@ -357,7 +391,9 @@ constructor( ), ), KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), value = !isFeatureDisabledByDevicePolicy() && featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), ), ), KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, Loading @@ -374,6 +410,16 @@ constructor( ) ) } } private suspend fun isFeatureDisabledByDevicePolicy(): Boolean { val flags = withContext(backgroundDispatcher) { devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) } val flagsToCheck = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL // TODO(b/268218507): "or" with DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL return flagsToCheck and flags != 0 } companion object { companion object { private const val TAG = "KeyguardQuickAffordanceInteractor" private const val TAG = "KeyguardQuickAffordanceInteractor" private const val DELIMITER = "::" private const val DELIMITER = "::" Loading packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard package com.android.systemui.keyguard import android.app.admin.DevicePolicyManager 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 Loading Loading @@ -90,6 +91,7 @@ class CustomizationProviderTest : SysuiTestCase() { @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: CustomizationProvider private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope private lateinit var testScope: TestScope Loading Loading @@ -183,6 +185,8 @@ class CustomizationProviderTest : SysuiTestCase() { featureFlags = featureFlags, featureFlags = featureFlags, repository = { quickAffordanceRepository }, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, launchAnimator = launchAnimator, devicePolicyManager = devicePolicyManager, backgroundDispatcher = testDispatcher, ) ) underTest.previewManager = underTest.previewManager = KeyguardRemotePreviewManager( KeyguardRemotePreviewManager( Loading packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +55 −43 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor import android.app.admin.DevicePolicyManager import android.content.Intent import android.content.Intent import android.os.UserHandle import android.os.UserHandle import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest Loading Loading @@ -54,7 +55,10 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.FakeSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest 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 Loading @@ -70,6 +74,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(Parameterized::class) @RunWith(Parameterized::class) class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { Loading Loading @@ -219,8 +224,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var expandable: Expandable @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: KeyguardQuickAffordanceInteractor private lateinit var underTest: KeyguardQuickAffordanceInteractor private lateinit var testScope: TestScope @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false Loading Loading @@ -292,6 +299,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.FACE_AUTH_REFACTOR, true) set(Flags.FACE_AUTH_REFACTOR, true) } } val testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) underTest = underTest = KeyguardQuickAffordanceInteractor( KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor = Loading Loading @@ -322,11 +331,14 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, featureFlags = featureFlags, repository = { quickAffordanceRepository }, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, launchAnimator = launchAnimator, devicePolicyManager = devicePolicyManager, backgroundDispatcher = testDispatcher, ) ) } } @Test @Test fun onQuickAffordanceTriggered() = runBlockingTest { fun onQuickAffordanceTriggered() = testScope.runTest { setUpMocks( setUpMocks( needStrongAuthAfterBoot = needStrongAuthAfterBoot, needStrongAuthAfterBoot = needStrongAuthAfterBoot, keyguardIsUnlocked = keyguardIsUnlocked, keyguardIsUnlocked = keyguardIsUnlocked, Loading Loading
packages/SystemUI/docs/device-entry/quickaffordance.md +5 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,11 @@ A picker experience may: * Unselect an already-selected quick affordance from a slot * Unselect an already-selected quick affordance from a slot * Unselect all already-selected quick affordances from a slot * Unselect all already-selected quick affordances from a slot ## Device Policy Returning `DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL` or `DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL` from `DevicePolicyManager#getKeyguardDisabledFeatures` will disable the keyguard quick affordance feature on the device. ## Testing ## Testing * Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` * Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` * Manually verify that your implementation works in multi-user environments from both the main user and a secondary user * Manually verify that your implementation works in multi-user environments from both the main user and a secondary user Loading
packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +6 −6 Original line number Original line Diff line number Diff line Loading @@ -131,7 +131,7 @@ class CustomizationProvider : throw UnsupportedOperationException() throw UnsupportedOperationException() } } return insertSelection(values) return runBlocking { insertSelection(values) } } } override fun query( override fun query( Loading Loading @@ -171,7 +171,7 @@ class CustomizationProvider : throw UnsupportedOperationException() throw UnsupportedOperationException() } } return deleteSelection(uri, selectionArgs) return runBlocking { deleteSelection(uri, selectionArgs) } } } override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { Loading @@ -189,7 +189,7 @@ class CustomizationProvider : } } } } private fun insertSelection(values: ContentValues?): Uri? { private suspend fun insertSelection(values: ContentValues?): Uri? { if (values == null) { if (values == null) { throw IllegalArgumentException("Cannot insert selection, no values passed in!") throw IllegalArgumentException("Cannot insert selection, no values passed in!") } } Loading Loading @@ -311,7 +311,7 @@ class CustomizationProvider : } } } } private fun querySlots(): Cursor { private suspend fun querySlots(): Cursor { return MatrixCursor( return MatrixCursor( arrayOf( arrayOf( Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, Loading @@ -330,7 +330,7 @@ class CustomizationProvider : } } } } private fun queryFlags(): Cursor { private suspend fun queryFlags(): Cursor { return MatrixCursor( return MatrixCursor( arrayOf( arrayOf( Contract.FlagsTable.Columns.NAME, Contract.FlagsTable.Columns.NAME, Loading @@ -353,7 +353,7 @@ class CustomizationProvider : } } } } private fun deleteSelection( private suspend fun deleteSelection( uri: Uri, uri: Uri, selectionArgs: Array<out String>?, selectionArgs: Array<out String>?, ): Int { ): Int { Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +54 −8 Original line number Original line Diff line number Diff line Loading @@ -18,12 +18,14 @@ package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor import android.app.AlertDialog import android.app.AlertDialog import android.app.admin.DevicePolicyManager import android.content.Intent import android.content.Intent import android.util.Log import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig Loading @@ -41,13 +43,17 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import dagger.Lazy import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @SysUISingleton class KeyguardQuickAffordanceInteractor class KeyguardQuickAffordanceInteractor @Inject @Inject Loading @@ -61,6 +67,8 @@ constructor( private val featureFlags: FeatureFlags, private val featureFlags: FeatureFlags, private val repository: Lazy<KeyguardQuickAffordanceRepository>, private val repository: Lazy<KeyguardQuickAffordanceRepository>, private val launchAnimator: DialogLaunchAnimator, private val launchAnimator: DialogLaunchAnimator, private val devicePolicyManager: DevicePolicyManager, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { ) { private val isUsingRepository: Boolean private val isUsingRepository: Boolean get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) Loading @@ -74,9 +82,13 @@ constructor( get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) /** Returns an observable for the quick affordance at the given position. */ /** Returns an observable for the quick affordance at the given position. */ fun quickAffordance( suspend fun quickAffordance( position: KeyguardQuickAffordancePosition position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceModel> { ): Flow<KeyguardQuickAffordanceModel> { if (isFeatureDisabledByDevicePolicy()) { return flowOf(KeyguardQuickAffordanceModel.Hidden) } return combine( return combine( quickAffordanceAlwaysVisible(position), quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, keyguardInteractor.isDozing, Loading Loading @@ -148,13 +160,20 @@ constructor( * * * @return `true` if the affordance was selected successfully; `false` otherwise. * @return `true` if the affordance was selected successfully; `false` otherwise. */ */ fun select(slotId: String, affordanceId: String): Boolean { suspend fun select(slotId: String, affordanceId: String): Boolean { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return false } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() val slot = slots.find { it.id == slotId } ?: return false val slot = slots.find { it.id == slotId } ?: return false val selections = val selections = repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() repository .get() .getCurrentSelections() .getOrDefault(slotId, emptyList()) .toMutableList() val alreadySelected = selections.remove(affordanceId) val alreadySelected = selections.remove(affordanceId) if (!alreadySelected) { if (!alreadySelected) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { Loading Loading @@ -183,8 +202,11 @@ constructor( * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if * the affordance was not on the slot to begin with). * the affordance was not on the slot to begin with). */ */ fun unselect(slotId: String, affordanceId: String?): Boolean { suspend fun unselect(slotId: String, affordanceId: String?): Boolean { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return false } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() if (slots.find { it.id == slotId } == null) { if (slots.find { it.id == slotId } == null) { Loading @@ -203,7 +225,11 @@ constructor( } } val selections = val selections = repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() repository .get() .getCurrentSelections() .getOrDefault(slotId, emptyList()) .toMutableList() return if (selections.remove(affordanceId)) { return if (selections.remove(affordanceId)) { repository repository .get() .get() Loading @@ -219,6 +245,10 @@ 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>> { if (isFeatureDisabledByDevicePolicy()) { return emptyMap() } val slots = repository.get().getSlotPickerRepresentations() val slots = repository.get().getSlotPickerRepresentations() val selections = repository.get().getCurrentSelections() val selections = repository.get().getCurrentSelections() val affordanceById = val affordanceById = Loading Loading @@ -343,13 +373,17 @@ constructor( return repository.get().getAffordancePickerRepresentations() return repository.get().getAffordancePickerRepresentations() } } fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { check(isUsingRepository) check(isUsingRepository) if (isFeatureDisabledByDevicePolicy()) { return emptyList() } return repository.get().getSlotPickerRepresentations() return repository.get().getSlotPickerRepresentations() } } fun getPickerFlags(): List<KeyguardPickerFlag> { suspend fun getPickerFlags(): List<KeyguardPickerFlag> { return listOf( return listOf( KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, Loading @@ -357,7 +391,9 @@ constructor( ), ), KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), value = !isFeatureDisabledByDevicePolicy() && featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), ), ), KeyguardPickerFlag( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, Loading @@ -374,6 +410,16 @@ constructor( ) ) } } private suspend fun isFeatureDisabledByDevicePolicy(): Boolean { val flags = withContext(backgroundDispatcher) { devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) } val flagsToCheck = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL // TODO(b/268218507): "or" with DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL return flagsToCheck and flags != 0 } companion object { companion object { private const val TAG = "KeyguardQuickAffordanceInteractor" private const val TAG = "KeyguardQuickAffordanceInteractor" private const val DELIMITER = "::" private const val DELIMITER = "::" Loading
packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard package com.android.systemui.keyguard import android.app.admin.DevicePolicyManager 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 Loading Loading @@ -90,6 +91,7 @@ class CustomizationProviderTest : SysuiTestCase() { @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: CustomizationProvider private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope private lateinit var testScope: TestScope Loading Loading @@ -183,6 +185,8 @@ class CustomizationProviderTest : SysuiTestCase() { featureFlags = featureFlags, featureFlags = featureFlags, repository = { quickAffordanceRepository }, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, launchAnimator = launchAnimator, devicePolicyManager = devicePolicyManager, backgroundDispatcher = testDispatcher, ) ) underTest.previewManager = underTest.previewManager = KeyguardRemotePreviewManager( KeyguardRemotePreviewManager( Loading
packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +55 −43 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor import android.app.admin.DevicePolicyManager import android.content.Intent import android.content.Intent import android.os.UserHandle import android.os.UserHandle import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest Loading Loading @@ -54,7 +55,10 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.FakeSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest 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 Loading @@ -70,6 +74,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(Parameterized::class) @RunWith(Parameterized::class) class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { Loading Loading @@ -219,8 +224,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var expandable: Expandable @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: KeyguardQuickAffordanceInteractor private lateinit var underTest: KeyguardQuickAffordanceInteractor private lateinit var testScope: TestScope @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false Loading Loading @@ -292,6 +299,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.FACE_AUTH_REFACTOR, true) set(Flags.FACE_AUTH_REFACTOR, true) } } val testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) underTest = underTest = KeyguardQuickAffordanceInteractor( KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor = Loading Loading @@ -322,11 +331,14 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, featureFlags = featureFlags, repository = { quickAffordanceRepository }, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, launchAnimator = launchAnimator, devicePolicyManager = devicePolicyManager, backgroundDispatcher = testDispatcher, ) ) } } @Test @Test fun onQuickAffordanceTriggered() = runBlockingTest { fun onQuickAffordanceTriggered() = testScope.runTest { setUpMocks( setUpMocks( needStrongAuthAfterBoot = needStrongAuthAfterBoot, needStrongAuthAfterBoot = needStrongAuthAfterBoot, keyguardIsUnlocked = keyguardIsUnlocked, keyguardIsUnlocked = keyguardIsUnlocked, Loading