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

This commit is contained in:
Alejandro Nijamkin
2022-07-10 18:26:19 +00:00
committed by Android (Google) Code Review
3 changed files with 358 additions and 20 deletions

View File

@@ -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
}
}

View File

@@ -17,27 +17,28 @@
package com.android.systemui.camera package com.android.systemui.camera
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions import android.app.ActivityOptions
import android.app.ActivityTaskManager import android.app.IActivityTaskManager
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.os.AsyncTask
import android.os.RemoteException import android.os.RemoteException
import android.os.UserHandle import android.os.UserHandle
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper 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.plugins.ActivityStarter
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.PanelViewController import com.android.systemui.statusbar.phone.PanelViewController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject import javax.inject.Inject
/** /**
@@ -52,8 +53,10 @@ class CameraGestureHelper @Inject constructor(
private val activityManager: ActivityManager, private val activityManager: ActivityManager,
private val activityStarter: ActivityStarter, private val activityStarter: ActivityStarter,
private val activityIntentHelper: ActivityIntentHelper, private val activityIntentHelper: ActivityIntentHelper,
private val activityTaskManager: IActivityTaskManager,
private val cameraIntents: CameraIntentsWrapper, private val cameraIntents: CameraIntentsWrapper,
private val contentResolver: ContentResolver, private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
) { ) {
/** /**
* Whether the camera application can be launched for the camera launch gesture. * Whether the camera application can be launched for the camera launch gesture.
@@ -63,15 +66,15 @@ class CameraGestureHelper @Inject constructor(
return false return false
} }
val resolveInfo: ResolveInfo = packageManager.resolveActivityAsUser( val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
getStartCameraIntent(), getStartCameraIntent(),
PackageManager.MATCH_DEFAULT_ONLY, PackageManager.MATCH_DEFAULT_ONLY,
KeyguardUpdateMonitor.getCurrentUser() KeyguardUpdateMonitor.getCurrentUser()
) )
val resolvedPackage = resolveInfo.activityInfo?.packageName val resolvedPackage = resolveInfo?.activityInfo?.packageName
return (resolvedPackage != null && return (resolvedPackage != null &&
(statusBarState != StatusBarState.SHADE || (statusBarState != StatusBarState.SHADE ||
!isForegroundApp(resolvedPackage))) !activityManager.isInForeground(resolvedPackage)))
} }
/** /**
@@ -85,8 +88,8 @@ class CameraGestureHelper @Inject constructor(
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity( val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser() intent, KeyguardUpdateMonitor.getCurrentUser()
) )
if (isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) { if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
AsyncTask.execute { uiExecutor.execute {
// Normally an activity will set its requested rotation animation on its window. // 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 // 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 // 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 = activityOptions.rotationAnimationHint =
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
try { try {
ActivityTaskManager.getService().startActivityAsUser( activityTaskManager.startActivityAsUser(
null, null,
context.basePackageName, context.basePackageName,
context.attributionTag, 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 { 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"
} }
} }

View File

@@ -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"
}
}