Merge "Unit test for CameraGestureHelper." into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d6ef72ba9c
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user