Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06acd509 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "Respects device policy to disble quick affordances" into tm-qpr-dev

parents e56827c6 9ab0eed7
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line Diff line number Diff line
@@ -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
+6 −6
Original line number Original line Diff line number Diff line
@@ -131,7 +131,7 @@ class CustomizationProvider :
            throw UnsupportedOperationException()
            throw UnsupportedOperationException()
        }
        }


        return insertSelection(values)
        return runBlocking { insertSelection(values) }
    }
    }


    override fun query(
    override fun query(
@@ -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? {
@@ -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!")
        }
        }
@@ -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,
@@ -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,
@@ -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 {
+54 −8
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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,
@@ -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) {
@@ -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) {
@@ -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()
@@ -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 =
@@ -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,
@@ -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,
@@ -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 = "::"
+4 −0
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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(
+55 −43
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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() {
@@ -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
@@ -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 =
@@ -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