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

Commit afc6a41e authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

Camera quick affordance respects device policy.

If the device admin policy disables the camera, neither the stills nor
the video camera quick affordances are available. If stills one also
respects the "secure camera" disablement flag, which the the video one
does not need to, because it always requires an unlock to access the
video camera (due to a missing camera API).

Bug: 268218507
Test: unit tests added
Test: temporarily faked DevicePolicyManager API and manually verified
that the camera and video camera options do not show up in wallpaper
picker.

Change-Id: Ia5b68283a25bc686ab11258a4b55b001fabcf562
parent d3d710e8
Loading
Loading
Loading
Loading
+15 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance

import android.app.StatusBarManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
import com.android.systemui.R
@@ -27,10 +28,14 @@ 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.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.withContext

@SysUISingleton
class CameraQuickAffordanceConfig
@@ -39,6 +44,9 @@ constructor(
    @Application private val context: Context,
    private val packageManager: PackageManager,
    private val cameraGestureHelper: Lazy<CameraGestureHelper>,
    private val userTracker: UserTracker,
    private val devicePolicyManager: DevicePolicyManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {

    override val key: String
@@ -79,7 +87,12 @@ constructor(
        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
    }

    private fun isLaunchable(): Boolean {
        return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
    private suspend fun isLaunchable(): Boolean {
        return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) &&
            withContext(backgroundDispatcher) {
                !devicePolicyManager.getCameraDisabled(null, userTracker.userId) &&
                    devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) and
                        DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA == 0
            }
    }
}
+15 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance

import android.app.StatusBarManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import com.android.systemui.ActivityIntentHelper
@@ -29,10 +30,13 @@ 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.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext

@SysUISingleton
class VideoCameraQuickAffordanceConfig
@@ -42,6 +46,8 @@ constructor(
    private val cameraIntents: CameraIntentsWrapper,
    private val activityIntentHelper: ActivityIntentHelper,
    private val userTracker: UserTracker,
    private val devicePolicyManager: DevicePolicyManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {

    private val intent: Intent by lazy {
@@ -63,8 +69,8 @@ constructor(
        get() = R.drawable.ic_videocam

    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
        get() =
            flowOf(
        get() = flow {
            emit(
                if (isLaunchable()) {
                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                        icon =
@@ -77,6 +83,7 @@ constructor(
                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden
                }
            )
        }

    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
        return if (isLaunchable()) {
@@ -95,11 +102,14 @@ constructor(
        )
    }

    private fun isLaunchable(): Boolean {
    private suspend fun isLaunchable(): Boolean {
        return activityIntentHelper.getTargetActivityInfo(
            intent,
            userTracker.userId,
            true,
        ) != null
        ) != null &&
            withContext(backgroundDispatcher) {
                !devicePolicyManager.getCameraDisabled(null, userTracker.userId)
            }
    }
}
+61 −16
Original line number Diff line number Diff line
@@ -18,15 +18,19 @@
package com.android.systemui.keyguard.data.quickaffordance

import android.app.StatusBarManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -44,21 +48,28 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
    @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
    @Mock private lateinit var context: Context
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var devicePolicyManager: DevicePolicyManager

    private lateinit var underTest: CameraQuickAffordanceConfig
    private lateinit var testScope: TestScope

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        setLaunchable(true)
        setLaunchable()

        val testDispatcher = StandardTestDispatcher()
        testScope = TestScope(testDispatcher)
        underTest =
            CameraQuickAffordanceConfig(
                context,
                packageManager,
            ) {
                cameraGestureHelper
            }
                { cameraGestureHelper },
                userTracker,
                devicePolicyManager,
                testDispatcher,
            )
    }

    @Test
@@ -73,7 +84,8 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
    }

    @Test
    fun `getPickerScreenState - default when launchable`() = runTest {
    fun `getPickerScreenState - default when launchable`() =
        testScope.runTest {
            setLaunchable(true)

            Truth.assertThat(underTest.getPickerScreenState())
@@ -81,15 +93,48 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
        setLaunchable(false)
    fun `getPickerScreenState - unavailable when camera app not installed`() =
        testScope.runTest {
            setLaunchable(isCameraAppInstalled = false)

            Truth.assertThat(underTest.getPickerScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
        }

    @Test
    fun `getPickerScreenState - unavailable when camera disabled by admin`() =
        testScope.runTest {
            setLaunchable(isCameraDisabledByDeviceAdmin = true)

            Truth.assertThat(underTest.getPickerScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
        }

    private fun setLaunchable(isLaunchable: Boolean) {
    @Test
    fun `getPickerScreenState - unavailable when secure camera disabled by admin`() =
        testScope.runTest {
            setLaunchable(isSecureCameraDisabledByDeviceAdmin = true)

            Truth.assertThat(underTest.getPickerScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
        }

    private fun setLaunchable(
        isCameraAppInstalled: Boolean = true,
        isCameraDisabledByDeviceAdmin: Boolean = false,
        isSecureCameraDisabledByDeviceAdmin: Boolean = false,
    ) {
        whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
            .thenReturn(isLaunchable)
            .thenReturn(isCameraAppInstalled)
        whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
            .thenReturn(isCameraDisabledByDeviceAdmin)
        whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
            .thenReturn(
                if (isSecureCameraDisabledByDeviceAdmin) {
                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
                } else {
                    0
                }
            )
    }
}
+66 −25
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package com.android.systemui.keyguard.data.quickaffordance

import android.app.admin.DevicePolicyManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ActivityIntentHelper
@@ -24,11 +25,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraIntentsWrapper
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
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
@@ -44,25 +48,34 @@ import org.mockito.MockitoAnnotations
class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {

    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
    @Mock private lateinit var devicePolicyManager: DevicePolicyManager

    private lateinit var underTest: VideoCameraQuickAffordanceConfig
    private lateinit var userTracker: UserTracker
    private lateinit var testScope: TestScope

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        val testDispatcher = StandardTestDispatcher()
        testScope = TestScope(testDispatcher)
        userTracker = FakeUserTracker()
        underTest =
            VideoCameraQuickAffordanceConfig(
                context = context,
                cameraIntents = CameraIntentsWrapper(context),
                activityIntentHelper = activityIntentHelper,
                userTracker = FakeUserTracker(),
                userTracker = userTracker,
                devicePolicyManager = devicePolicyManager,
                backgroundDispatcher = testDispatcher,
            )
    }

    @Test
    fun `lockScreenState - visible when launchable`() = runTest {
        setLaunchable(true)
    fun `lockScreenState - visible when launchable`() =
        testScope.runTest {
            setLaunchable()

            val lockScreenState = collectLastValue(underTest.lockScreenState)

@@ -71,8 +84,9 @@ class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun `lockScreenState - hidden when not launchable`() = runTest {
        setLaunchable(false)
    fun `lockScreenState - hidden when app not installed on device`() =
        testScope.runTest {
            setLaunchable(isVideoCameraAppInstalled = false)

            val lockScreenState = collectLastValue(underTest.lockScreenState)

@@ -81,22 +95,47 @@ class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun `getPickerScreenState - default when launchable`() = runTest {
        setLaunchable(true)
    fun `lockScreenState - hidden when camera disabled by admin`() =
        testScope.runTest {
            setLaunchable(isCameraDisabledByAdmin = true)

            val lockScreenState = collectLastValue(underTest.lockScreenState)

            assertThat(lockScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
        }

    @Test
    fun `getPickerScreenState - default when launchable`() =
        testScope.runTest {
            setLaunchable()

            assertThat(underTest.getPickerScreenState())
                .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
        }

    @Test
    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
        setLaunchable(false)
    fun `getPickerScreenState - unavailable when app not installed on device`() =
        testScope.runTest {
            setLaunchable(isVideoCameraAppInstalled = false)

            assertThat(underTest.getPickerScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
        }

    @Test
    fun `getPickerScreenState - unavailable when camera disabled by admin`() =
        testScope.runTest {
            setLaunchable(isCameraDisabledByAdmin = true)

            assertThat(underTest.getPickerScreenState())
                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
        }

    private fun setLaunchable(isLaunchable: Boolean) {
    private fun setLaunchable(
        isVideoCameraAppInstalled: Boolean = true,
        isCameraDisabledByAdmin: Boolean = false,
    ) {
        whenever(
                activityIntentHelper.getTargetActivityInfo(
                    any(),
@@ -105,11 +144,13 @@ class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
                )
            )
            .thenReturn(
                if (isLaunchable) {
                if (isVideoCameraAppInstalled) {
                    mock()
                } else {
                    null
                }
            )
        whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
            .thenReturn(isCameraDisabledByAdmin)
    }
}