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

Commit d6ef72ba authored by Alejandro Nijamkin's avatar Alejandro Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "Unit test for CameraGestureHelper." into tm-qpr-dev

parents 231b56c7 4af7cc97
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.system

import android.app.ActivityManager

/** Kotlin extensions for [ActivityManager] */
object ActivityManagerKt {

    /**
     * Returns `true` whether the app with the given package name has an activity at the top of the
     * most recent task; `false` otherwise
     */
    fun ActivityManager.isInForeground(packageName: String): Boolean {
        val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
    }
}
+15 −20
Original line number Diff line number Diff line
@@ -17,27 +17,28 @@
package com.android.systemui.camera

import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.ActivityTaskManager
import android.app.IActivityTaskManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.AsyncTask
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.camera.CameraIntents.Companion.isSecureCameraIntent
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.PanelViewController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject

/**
@@ -52,8 +53,10 @@ class CameraGestureHelper @Inject constructor(
    private val activityManager: ActivityManager,
    private val activityStarter: ActivityStarter,
    private val activityIntentHelper: ActivityIntentHelper,
    private val activityTaskManager: IActivityTaskManager,
    private val cameraIntents: CameraIntentsWrapper,
    private val contentResolver: ContentResolver,
    @Main private val uiExecutor: Executor,
) {
    /**
     * Whether the camera application can be launched for the camera launch gesture.
@@ -63,15 +66,15 @@ class CameraGestureHelper @Inject constructor(
            return false
        }

        val resolveInfo: ResolveInfo = packageManager.resolveActivityAsUser(
        val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
            getStartCameraIntent(),
            PackageManager.MATCH_DEFAULT_ONLY,
            KeyguardUpdateMonitor.getCurrentUser()
        )
        val resolvedPackage = resolveInfo.activityInfo?.packageName
        val resolvedPackage = resolveInfo?.activityInfo?.packageName
        return (resolvedPackage != null &&
                (statusBarState != StatusBarState.SHADE ||
                !isForegroundApp(resolvedPackage)))
                !activityManager.isInForeground(resolvedPackage)))
    }

    /**
@@ -85,8 +88,8 @@ class CameraGestureHelper @Inject constructor(
        val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
            intent, KeyguardUpdateMonitor.getCurrentUser()
        )
        if (isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
            AsyncTask.execute {
        if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
            uiExecutor.execute {
                // Normally an activity will set its requested rotation animation on its window.
                // However when launching an activity causes the orientation to change this is too
                // late. In these cases, the default animation is used. This doesn't look good for
@@ -98,7 +101,7 @@ class CameraGestureHelper @Inject constructor(
                activityOptions.rotationAnimationHint =
                    WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
                try {
                    ActivityTaskManager.getService().startActivityAsUser(
                    activityTaskManager.startActivityAsUser(
                        null,
                        context.basePackageName,
                        context.attributionTag,
@@ -148,16 +151,8 @@ class CameraGestureHelper @Inject constructor(
        }
    }

    /**
     * Returns `true` if the application with the given package name is running in the foreground;
     * `false` otherwise
     */
    private fun isForegroundApp(packageName: String): Boolean {
        val tasks: List<RunningTaskInfo> = activityManager.getRunningTasks(1)
        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
    }

    companion object {
        private const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
        @VisibleForTesting
        const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
    }
}
+311 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.camera

import android.app.ActivityManager
import android.app.IActivityTaskManager
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import androidx.test.filters.SmallTest
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(JUnit4::class)
class CameraGestureHelperTest : SysuiTestCase() {

    @Mock
    lateinit var centralSurfaces: CentralSurfaces
    @Mock
    lateinit var keyguardStateController: KeyguardStateController
    @Mock
    lateinit var packageManager: PackageManager
    @Mock
    lateinit var activityManager: ActivityManager
    @Mock
    lateinit var activityStarter: ActivityStarter
    @Mock
    lateinit var activityIntentHelper: ActivityIntentHelper
    @Mock
    lateinit var activityTaskManager: IActivityTaskManager
    @Mock
    lateinit var cameraIntents: CameraIntentsWrapper
    @Mock
    lateinit var contentResolver: ContentResolver

    private lateinit var underTest: CameraGestureHelper

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        whenever(cameraIntents.getSecureCameraIntent()).thenReturn(
            Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
        )
        whenever(cameraIntents.getInsecureCameraIntent()).thenReturn(
            Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
        )

        prepare()

        underTest = CameraGestureHelper(
            context = mock(),
            centralSurfaces = centralSurfaces,
            keyguardStateController = keyguardStateController,
            packageManager = packageManager,
            activityManager = activityManager,
            activityStarter = activityStarter,
            activityIntentHelper = activityIntentHelper,
            activityTaskManager = activityTaskManager,
            cameraIntents = cameraIntents,
            contentResolver = contentResolver,
            uiExecutor = MoreExecutors.directExecutor(),
        )
    }

    /**
     * Prepares for tests by setting up the various mocks to emulate a specific device state.
     *
     * <p>Safe to call multiple times in a single test (for example, once in [setUp] and once in the
     * actual test case).
     *
     * @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
     * @param installedCameraAppCount The number of installed camera apps on the device
     * @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
     * set with a "secure" option that requires the user to provide some secret/credentials to be
     * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
     * non-secure options are "None" and "Swipe"
     * @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
     * most recent/current task of activities
     * @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
     * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
     */
    private fun prepare(
        isCameraAllowedByAdmin: Boolean = true,
        installedCameraAppCount: Int = 1,
        isUsingSecureScreenLockOption: Boolean = true,
        isCameraActivityRunningOnTop: Boolean = false,
        isTaskListEmpty: Boolean = false,
    ) {
        whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)

        whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
            .thenReturn(installedCameraAppCount > 1)

        whenever(keyguardStateController.isMethodSecure).thenReturn(isUsingSecureScreenLockOption)
        whenever(keyguardStateController.canDismissLockScreen())
            .thenReturn(!isUsingSecureScreenLockOption)

        if (installedCameraAppCount >= 1) {
            val resolveInfo = ResolveInfo().apply {
                this.activityInfo = ActivityInfo().apply {
                    packageName = CAMERA_APP_PACKAGE_NAME
                }
            }
            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
                resolveInfo
            )
        } else {
            whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
                null
            )
        }

        when {
            isCameraActivityRunningOnTop -> {
                val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
                    topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
                }
                whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
                    listOf(
                        runningTaskInfo
                    )
                )
            }
            isTaskListEmpty -> {
                whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
            }
            else -> {
                whenever(activityManager.getRunningTasks(anyInt())).thenReturn(listOf())
            }
        }
    }

    @Test
    fun `canCameraGestureBeLaunched - status bar state is keyguard - returns true`() {
        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is shade-locked - returns true`() {
        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is keyguard - camera activity on top - returns true`() {
        prepare(isCameraActivityRunningOnTop = true)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is shade-locked - camera activity on top - true`() {
        prepare(isCameraActivityRunningOnTop = true)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
    }

    @Test
    fun `canCameraGestureBeLaunched - not allowed by admin - returns false`() {
        prepare(isCameraAllowedByAdmin = false)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
    }

    @Test
    fun `canCameraGestureBeLaunched - intent does not resolve to any app - returns false`() {
        prepare(installedCameraAppCount = 0)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is shade - no running tasks - returns true`() {
        prepare(isCameraActivityRunningOnTop = false, isTaskListEmpty = true)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is shade - camera activity on top - returns false`() {
        prepare(isCameraActivityRunningOnTop = true)

        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isFalse()
    }

    @Test
    fun `canCameraGestureBeLaunched - state is shade - camera activity not on top - true`() {
        assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
    }

    @Test
    fun `launchCamera - only one camera app installed - using secure screen lock option`() {
        val source = 1337

        underTest.launchCamera(source)

        assertActivityStarting(isSecure = true, source = source)
    }

    @Test
    fun `launchCamera - only one camera app installed - using non-secure screen lock option`() {
        prepare(isUsingSecureScreenLockOption = false)
        val source = 1337

        underTest.launchCamera(source)

        assertActivityStarting(isSecure = false, source = source)
    }

    @Test
    fun `launchCamera - multiple camera apps installed - using secure screen lock option`() {
        prepare(installedCameraAppCount = 2)
        val source = 1337

        underTest.launchCamera(source)

        assertActivityStarting(
            isSecure = true,
            source = source,
            moreThanOneCameraAppInstalled = true
        )
    }

    @Test
    fun `launchCamera - multiple camera apps installed - using non-secure screen lock option`() {
        prepare(
            isUsingSecureScreenLockOption = false,
            installedCameraAppCount = 2,
        )
        val source = 1337

        underTest.launchCamera(source)

        assertActivityStarting(
            isSecure = false,
            moreThanOneCameraAppInstalled = true,
            source = source
        )
    }

    private fun assertActivityStarting(
        isSecure: Boolean,
        source: Int,
        moreThanOneCameraAppInstalled: Boolean = false,
    ) {
        val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
        if (isSecure && !moreThanOneCameraAppInstalled) {
            verify(activityTaskManager).startActivityAsUser(
                any(),
                any(),
                any(),
                intentCaptor.capture(),
                any(),
                any(),
                any(),
                anyInt(),
                anyInt(),
                any(),
                any(),
                anyInt()
            )
        } else {
            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
        }
        val intent = intentCaptor.value

        assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
        assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
            .isEqualTo(source)
    }

    companion object {
        private const val CAMERA_APP_PACKAGE_NAME = "cameraAppPackageName"
    }
}