Loading packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt 0 → 100644 +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 } } packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +15 −20 Original line number Diff line number Diff line Loading @@ -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 /** Loading @@ -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. Loading @@ -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))) } /** Loading @@ -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 Loading @@ -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, Loading Loading @@ -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" } } packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt 0 → 100644 +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" } } Loading
packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt 0 → 100644 +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 } }
packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +15 −20 Original line number Diff line number Diff line Loading @@ -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 /** Loading @@ -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. Loading @@ -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))) } /** Loading @@ -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 Loading @@ -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, Loading Loading @@ -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" } }
packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt 0 → 100644 +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" } }