1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.camera
18 
19 import android.app.ActivityManager
20 import android.app.IActivityTaskManager
21 import android.content.ComponentName
22 import android.content.ContentResolver
23 import android.content.Intent
24 import android.content.pm.ActivityInfo
25 import android.content.pm.PackageManager
26 import android.content.pm.ResolveInfo
27 import androidx.test.filters.SmallTest
28 import com.android.systemui.ActivityIntentHelper
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.plugins.ActivityStarter
31 import com.android.systemui.settings.UserTracker
32 import com.android.systemui.statusbar.StatusBarState
33 import com.android.systemui.statusbar.phone.CentralSurfaces
34 import com.android.systemui.statusbar.policy.KeyguardStateController
35 import com.android.systemui.util.mockito.KotlinArgumentCaptor
36 import com.android.systemui.util.mockito.eq
37 import com.android.systemui.util.mockito.mock
38 import com.google.common.truth.Truth.assertThat
39 import com.google.common.util.concurrent.MoreExecutors
40 import org.junit.Before
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.junit.runners.JUnit4
44 import org.mockito.Mock
45 import org.mockito.Mockito.any
46 import org.mockito.Mockito.anyInt
47 import org.mockito.Mockito.verify
48 import org.mockito.Mockito.`when` as whenever
49 import org.mockito.MockitoAnnotations
50 
51 @SmallTest
52 @RunWith(JUnit4::class)
53 class CameraGestureHelperTest : SysuiTestCase() {
54 
55     @Mock
56     lateinit var centralSurfaces: CentralSurfaces
57     @Mock
58     lateinit var keyguardStateController: KeyguardStateController
59     @Mock
60     lateinit var packageManager: PackageManager
61     @Mock
62     lateinit var activityManager: ActivityManager
63     @Mock
64     lateinit var activityStarter: ActivityStarter
65     @Mock
66     lateinit var activityIntentHelper: ActivityIntentHelper
67     @Mock
68     lateinit var activityTaskManager: IActivityTaskManager
69     @Mock
70     lateinit var cameraIntents: CameraIntentsWrapper
71     @Mock
72     lateinit var contentResolver: ContentResolver
73     @Mock
74     lateinit var userTracker: UserTracker
75 
76     private lateinit var underTest: CameraGestureHelper
77 
78     @Before
79     fun setUp() {
80         MockitoAnnotations.initMocks(this)
81         whenever(cameraIntents.getSecureCameraIntent()).thenReturn(
82             Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
83         )
84         whenever(cameraIntents.getInsecureCameraIntent()).thenReturn(
85             Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
86         )
87 
88         prepare()
89 
90         underTest = CameraGestureHelper(
91             context = mock(),
92             centralSurfaces = centralSurfaces,
93             keyguardStateController = keyguardStateController,
94             packageManager = packageManager,
95             activityManager = activityManager,
96             activityStarter = activityStarter,
97             activityIntentHelper = activityIntentHelper,
98             activityTaskManager = activityTaskManager,
99             cameraIntents = cameraIntents,
100             contentResolver = contentResolver,
101             uiExecutor = MoreExecutors.directExecutor(),
102             userTracker = userTracker,
103         )
104     }
105 
106     /**
107      * Prepares for tests by setting up the various mocks to emulate a specific device state.
108      *
109      * <p>Safe to call multiple times in a single test (for example, once in [setUp] and once in the
110      * actual test case).
111      *
112      * @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
113      * @param installedCameraAppCount The number of installed camera apps on the device
114      * @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
115      * set with a "secure" option that requires the user to provide some secret/credentials to be
116      * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
117      * non-secure options are "None" and "Swipe"
118      * @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
119      * most recent/current task of activities
120      * @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
121      * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
122      */
123     private fun prepare(
124         isCameraAllowedByAdmin: Boolean = true,
125         installedCameraAppCount: Int = 1,
126         isUsingSecureScreenLockOption: Boolean = true,
127         isCameraActivityRunningOnTop: Boolean = false,
128         isTaskListEmpty: Boolean = false,
129     ) {
130         whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)
131 
132         whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
133             .thenReturn(installedCameraAppCount > 1)
134 
135         whenever(keyguardStateController.isMethodSecure).thenReturn(isUsingSecureScreenLockOption)
136         whenever(keyguardStateController.canDismissLockScreen())
137             .thenReturn(!isUsingSecureScreenLockOption)
138 
139         if (installedCameraAppCount >= 1) {
140             val resolveInfo = ResolveInfo().apply {
141                 this.activityInfo = ActivityInfo().apply {
142                     packageName = CAMERA_APP_PACKAGE_NAME
143                 }
144             }
145             whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
146                 resolveInfo
147             )
148         } else {
149             whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
150                 null
151             )
152         }
153 
154         when {
155             isCameraActivityRunningOnTop -> {
156                 val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
157                     topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
158                 }
159                 whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
160                     listOf(
161                         runningTaskInfo
162                     )
163                 )
164             }
165             isTaskListEmpty -> {
166                 whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
167             }
168             else -> {
169                 whenever(activityManager.getRunningTasks(anyInt())).thenReturn(listOf())
170             }
171         }
172     }
173 
174     @Test
175     fun canCameraGestureBeLaunched_statusBarStateIsKeyguard_returnsTrue() {
176         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
177     }
178 
179     @Test
180     fun canCameraGestureBeLaunched_stateIsShadeLocked_returnsTrue() {
181         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
182     }
183 
184     @Test
185     fun canCameraGestureBeLaunched_stateIsKeyguard_cameraActivityOnTop_returnsTrue() {
186         prepare(isCameraActivityRunningOnTop = true)
187 
188         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
189     }
190 
191     @Test
192     fun canCameraGestureBeLaunched_stateIsShadeLocked_cameraActivityOnTop_true() {
193         prepare(isCameraActivityRunningOnTop = true)
194 
195         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
196     }
197 
198     @Test
199     fun canCameraGestureBeLaunched_notAllowedByAdmin_returnsFalse() {
200         prepare(isCameraAllowedByAdmin = false)
201 
202         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
203     }
204 
205     @Test
206     fun canCameraGestureBeLaunched_intentDoesNotResolveToAnyApp_returnsFalse() {
207         prepare(installedCameraAppCount = 0)
208 
209         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
210     }
211 
212     @Test
213     fun canCameraGestureBeLaunched_stateIsShade_noRunningTasks_returnsTrue() {
214         prepare(isCameraActivityRunningOnTop = false, isTaskListEmpty = true)
215 
216         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
217     }
218 
219     @Test
220     fun canCameraGestureBeLaunched_stateIsShade_cameraActivityOnTop_returnsFalse() {
221         prepare(isCameraActivityRunningOnTop = true)
222 
223         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isFalse()
224     }
225 
226     @Test
227     fun canCameraGestureBeLaunched_stateIsShade_cameraActivityNotOnTop_true() {
228         assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
229     }
230 
231     @Test
232     fun launchCamera_onlyOneCameraAppInstalled_usingSecureScreenLockOption() {
233         val source = 1337
234 
235         underTest.launchCamera(source)
236 
237         assertActivityStarting(isSecure = true, source = source)
238     }
239 
240     @Test
241     fun launchCamera_onlyOneCameraAppInstalled_usingNonSecureScreenLockOption() {
242         prepare(isUsingSecureScreenLockOption = false)
243         val source = 1337
244 
245         underTest.launchCamera(source)
246 
247         assertActivityStarting(isSecure = false, source = source)
248     }
249 
250     @Test
251     fun launchCamera_multipleCameraAppsInstalled_usingSecureScreenLockOption() {
252         prepare(installedCameraAppCount = 2)
253         val source = 1337
254 
255         underTest.launchCamera(source)
256 
257         assertActivityStarting(
258             isSecure = true,
259             source = source,
260             moreThanOneCameraAppInstalled = true
261         )
262     }
263 
264     @Test
265     fun launchCamera_multipleCameraAppsInstalled_usingNonSecureScreenLockOption() {
266         prepare(
267             isUsingSecureScreenLockOption = false,
268             installedCameraAppCount = 2,
269         )
270         val source = 1337
271 
272         underTest.launchCamera(source)
273 
274         assertActivityStarting(
275             isSecure = false,
276             moreThanOneCameraAppInstalled = true,
277             source = source
278         )
279     }
280 
281     private fun assertActivityStarting(
282         isSecure: Boolean,
283         source: Int,
284         moreThanOneCameraAppInstalled: Boolean = false,
285     ) {
286         val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
287         if (isSecure && !moreThanOneCameraAppInstalled) {
288             verify(activityTaskManager).startActivityAsUser(
289                 any(),
290                 any(),
291                 any(),
292                 intentCaptor.capture(),
293                 any(),
294                 any(),
295                 any(),
296                 anyInt(),
297                 anyInt(),
298                 any(),
299                 any(),
300                 anyInt()
301             )
302         } else {
303             verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
304         }
305         val intent = intentCaptor.value
306 
307         assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
308         assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
309             .isEqualTo(source)
310     }
311 
312     companion object {
313         private const val CAMERA_APP_PACKAGE_NAME = "cameraAppPackageName"
314     }
315 }
316