Merge "Respects device policy to disble quick affordances" into tm-qpr-dev am: 06acd50977 am: fac8e2cf00

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

Change-Id: I431cbe3c1cce2331b7bf0315c0acff66bc96e285
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Ale Nijamkin
2023-02-17 06:27:13 +00:00
committed by Automerger Merge Worker
7 changed files with 176 additions and 56 deletions

View File

@@ -52,6 +52,11 @@ A picker experience may:
* Unselect an already-selected quick affordance 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
* 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

View File

@@ -131,7 +131,7 @@ class CustomizationProvider :
throw UnsupportedOperationException()
}
return insertSelection(values)
return runBlocking { insertSelection(values) }
}
override fun query(
@@ -171,7 +171,7 @@ class CustomizationProvider :
throw UnsupportedOperationException()
}
return deleteSelection(uri, selectionArgs)
return runBlocking { deleteSelection(uri, selectionArgs) }
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
@@ -189,7 +189,7 @@ class CustomizationProvider :
}
}
private fun insertSelection(values: ContentValues?): Uri? {
private suspend fun insertSelection(values: ContentValues?): Uri? {
if (values == null) {
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
}
@@ -311,7 +311,7 @@ class CustomizationProvider :
}
}
private fun querySlots(): Cursor {
private suspend fun querySlots(): Cursor {
return MatrixCursor(
arrayOf(
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID,
@@ -330,7 +330,7 @@ class CustomizationProvider :
}
}
private fun queryFlags(): Cursor {
private suspend fun queryFlags(): Cursor {
return MatrixCursor(
arrayOf(
Contract.FlagsTable.Columns.NAME,
@@ -353,7 +353,7 @@ class CustomizationProvider :
}
}
private fun deleteSelection(
private suspend fun deleteSelection(
uri: Uri,
selectionArgs: Array<out String>?,
): Int {

View File

@@ -18,12 +18,14 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.AlertDialog
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -41,13 +43,17 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
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
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceInteractor
@Inject
@@ -61,6 +67,8 @@ constructor(
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
private val launchAnimator: DialogLaunchAnimator,
private val devicePolicyManager: DevicePolicyManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
private val isUsingRepository: Boolean
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
@@ -74,9 +82,13 @@ constructor(
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
suspend fun quickAffordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
if (isFeatureDisabledByDevicePolicy()) {
return flowOf(KeyguardQuickAffordanceModel.Hidden)
}
return combine(
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
@@ -148,8 +160,11 @@ constructor(
*
* @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)
if (isFeatureDisabledByDevicePolicy()) {
return false
}
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
@@ -187,8 +202,11 @@ constructor(
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* 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)
if (isFeatureDisabledByDevicePolicy()) {
return false
}
val slots = repository.get().getSlotPickerRepresentations()
if (slots.find { it.id == slotId } == null) {
@@ -227,6 +245,10 @@ constructor(
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
if (isFeatureDisabledByDevicePolicy()) {
return emptyMap()
}
val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getCurrentSelections()
val affordanceById =
@@ -351,13 +373,17 @@ constructor(
return repository.get().getAffordancePickerRepresentations()
}
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
check(isUsingRepository)
if (isFeatureDisabledByDevicePolicy()) {
return emptyList()
}
return repository.get().getSlotPickerRepresentations()
}
fun getPickerFlags(): List<KeyguardPickerFlag> {
suspend fun getPickerFlags(): List<KeyguardPickerFlag> {
return listOf(
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
@@ -365,7 +391,9 @@ constructor(
),
KeyguardPickerFlag(
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(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
@@ -382,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 {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard
import android.app.admin.DevicePolicyManager
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
@@ -90,6 +91,7 @@ class CustomizationProviderTest : SysuiTestCase() {
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
@@ -183,6 +185,8 @@ class CustomizationProviderTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
underTest.previewManager =
KeyguardRemotePreviewManager(

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
import androidx.test.filters.SmallTest
@@ -54,7 +55,10 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
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.Test
import org.junit.runner.RunWith
@@ -70,6 +74,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@@ -219,8 +224,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
@JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
@@ -292,6 +299,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
}
val testDispatcher = StandardTestDispatcher()
testScope = TestScope(testDispatcher)
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
@@ -322,58 +331,61 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
}
@Test
fun onQuickAffordanceTriggered() = runBlockingTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
fun onQuickAffordanceTriggered() =
testScope.runTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
homeControls.setState(
lockScreenState =
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = DRAWABLE,
)
)
homeControls.onTriggeredResult =
if (startActivity) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
} else {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
underTest.onQuickAffordanceTriggered(
configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
homeControls.setState(
lockScreenState =
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = DRAWABLE,
)
)
homeControls.onTriggeredResult =
if (startActivity) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
if (needsToUnlockFirst) {
verify(activityStarter)
.postStartActivityDismissingKeyguard(
any(),
/* delay= */ eq(0),
same(animationController),
)
} else {
verify(activityStarter)
.startActivity(
any(),
/* dismissShade= */ eq(true),
same(animationController),
/* showOverLockscreenWhenLocked= */ eq(true),
)
}
} else {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
verifyZeroInteractions(activityStarter)
}
underTest.onQuickAffordanceTriggered(
configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
if (startActivity) {
if (needsToUnlockFirst) {
verify(activityStarter)
.postStartActivityDismissingKeyguard(
any(),
/* delay= */ eq(0),
same(animationController),
)
} else {
verify(activityStarter)
.startActivity(
any(),
/* dismissShade= */ eq(true),
same(animationController),
/* showOverLockscreenWhenLocked= */ eq(true),
)
}
} else {
verifyZeroInteractions(activityStarter)
}
}
private fun setUpMocks(
needStrongAuthAfterBoot: Boolean = true,

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.admin.DevicePolicyManager
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -78,6 +79,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -184,6 +186,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
}
@@ -238,6 +242,25 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
}
@Test
fun `quickAffordance - bottom end affordance is hidden when disabled by device policy`() =
testScope.runTest {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
val collectedValue by
collectLastValue(
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
)
assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
}
@Test
fun `quickAffordance - bottom start affordance hidden while dozing`() =
testScope.runTest {

View File

@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
import androidx.test.filters.SmallTest
@@ -87,6 +88,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardBottomAreaViewModel
@@ -140,6 +142,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
bouncerRepository = FakeKeyguardBouncerRepository(),
)
whenever(userTracker.userHandle).thenReturn(mock())
whenever(userTracker.userId).thenReturn(10)
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val testDispatcher = StandardTestDispatcher()
@@ -205,6 +208,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -239,6 +244,39 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
)
}
@Test
fun `startButton - hidden when device policy disables all keyguard features`() =
testScope.runTest {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
repository.setKeyguardShowing(true)
val latest by collectLastValue(underTest.startButton)
val testConfig =
TestConfig(
isVisible = true,
isClickable = true,
isActivated = true,
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
)
val configKey =
setUpQuickAffordanceModel(
position = KeyguardQuickAffordancePosition.BOTTOM_START,
testConfig = testConfig,
)
assertQuickAffordanceViewModel(
viewModel = latest,
testConfig =
TestConfig(
isVisible = false,
),
configKey = configKey,
)
}
@Test
fun `startButton - in preview mode - visible even when keyguard not showing`() =
testScope.runTest {