1 /*
<lambda>null2  * 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 android.sensorprivacy.cts
18 
19 import android.app.KeyguardManager
20 import android.app.AppOpsManager
21 import android.content.Intent
22 import android.content.pm.PackageManager
23 import android.hardware.SensorPrivacyManager
24 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener
25 import android.os.PowerManager
26 import android.platform.test.annotations.AppModeFull
27 import android.hardware.SensorPrivacyManager.Sensors.CAMERA
28 import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
29 import android.hardware.SensorPrivacyManager.Sources.OTHER
30 import android.support.test.uiautomator.By
31 import android.view.KeyEvent
32 import androidx.test.platform.app.InstrumentationRegistry
33 import androidx.test.uiautomator.UiDevice
34 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
35 import com.android.compatibility.common.util.SystemUtil.eventually
36 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
37 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
38 import com.android.compatibility.common.util.UiAutomatorUtils
39 import org.junit.After
40 import org.junit.Assert.assertEquals
41 import org.junit.Assert.assertFalse
42 import org.junit.Assert.assertNull
43 import org.junit.Assert.assertTrue
44 import org.junit.Assume
45 import org.junit.Before
46 import org.junit.Test
47 import java.util.concurrent.CountDownLatch
48 import java.util.concurrent.Executors
49 import java.util.concurrent.TimeUnit
50 import java.util.regex.Pattern
51 
52 abstract class SensorPrivacyBaseTest(
53     val sensor: Int,
54     vararg val extras: String
55 ) {
56 
57     companion object {
58         const val MIC_CAM_ACTIVITY_ACTION =
59                 "android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM"
60         const val FINISH_MIC_CAM_ACTIVITY_ACTION =
61                 "android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
62         const val USE_MIC_EXTRA =
63                 "android.sensorprivacy.cts.usemiccamera.extra.USE_MICROPHONE"
64         const val USE_CAM_EXTRA =
65                 "android.sensorprivacy.cts.usemiccamera.extra.USE_CAMERA"
66         const val DELAYED_ACTIVITY_EXTRA =
67                 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY"
68         const val DELAYED_ACTIVITY_NEW_TASK_EXTRA =
69                 "android.sensorprivacy.cts.usemiccamera.extra.DELAYED_ACTIVITY_NEW_TASK"
70         const val PKG_NAME = "android.sensorprivacy.cts.usemiccamera"
71         const val RECORDING_FILE_NAME = "${PKG_NAME}_record.mp4"
72         const val ACTIVITY_TITLE_SNIP = "CtsUseMic"
73         const val SENSOR_USE_TIME_MS = 5L
74     }
75 
76     protected val instrumentation = InstrumentationRegistry.getInstrumentation()!!
77     protected val uiAutomation = instrumentation.uiAutomation!!
78     protected val uiDevice = UiDevice.getInstance(instrumentation)!!
79     protected val context = instrumentation.targetContext!!
80     protected val spm = context.getSystemService(SensorPrivacyManager::class.java)!!
81     protected val packageManager = context.packageManager!!
82     protected val op = getOpForSensor(sensor)
83 
84     var oldState: Boolean = false
85 
86     @Before
87     fun init() {
88         oldState = isSensorPrivacyEnabled()
89         setSensor(false)
90         Assume.assumeTrue(spm.supportsSensorToggle(sensor))
91         uiDevice.wakeUp()
92         runShellCommandOrThrow("wm dismiss-keyguard")
93         uiDevice.waitForIdle()
94     }
95 
96     @After
97     fun tearDown() {
98         finishTestApp()
99         Thread.sleep(3000)
100         setSensor(oldState)
101     }
102 
103     @Test
104     fun testSetSensor() {
105         setSensor(true)
106         assertTrue(isSensorPrivacyEnabled())
107 
108         setSensor(false)
109         assertFalse(isSensorPrivacyEnabled())
110     }
111 
112     @Test
113     fun testDialog() {
114         testDialog(delayedActivity = false, delayedActivityNewTask = false)
115     }
116 
117     @Test
118     fun testDialog_remainsOnTop() {
119         testDialog(delayedActivity = true, delayedActivityNewTask = false)
120     }
121 
122     @Test
123     fun testDialog_remainsOnTop_newTask() {
124         testDialog(delayedActivity = true, delayedActivityNewTask = true)
125     }
126 
127     fun testDialog(delayedActivity: Boolean = false, delayedActivityNewTask: Boolean = false) {
128         try {
129             setSensor(true)
130             val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
131                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
132                     .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
133             for (extra in extras) {
134                 intent.putExtra(extra, true)
135             }
136             intent.putExtra(DELAYED_ACTIVITY_EXTRA, delayedActivity)
137             intent.putExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, delayedActivityNewTask)
138             context.startActivity(intent)
139             if (delayedActivity || delayedActivityNewTask) {
140                 Thread.sleep(3000)
141             }
142             unblockSensorWithDialogAndAssert()
143         } finally {
144             runShellCommandOrThrow("am broadcast" +
145                     " --user ${context.userId}" +
146                     " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" +
147                     " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}")
148         }
149     }
150 
151     @Test
152     fun testListener() {
153         val executor = Executors.newSingleThreadExecutor()
154         setSensor(false)
155         val latchEnabled = CountDownLatch(1)
156         var listener =
157                 OnSensorPrivacyChangedListener { _, enabled: Boolean ->
158                     if (enabled) {
159                         latchEnabled.countDown()
160                     }
161                 }
162         runWithShellPermissionIdentity {
163             spm.addSensorPrivacyListener(sensor, executor, listener)
164         }
165         setSensor(true)
166         latchEnabled.await(100, TimeUnit.MILLISECONDS)
167         runWithShellPermissionIdentity {
168             spm.removeSensorPrivacyListener(sensor, listener)
169         }
170 
171         val latchDisabled = CountDownLatch(1)
172         listener = OnSensorPrivacyChangedListener { _, enabled: Boolean ->
173             if (!enabled) {
174                 latchDisabled.countDown()
175             }
176         }
177         runWithShellPermissionIdentity {
178             spm.addSensorPrivacyListener(sensor, executor, listener)
179         }
180         setSensor(false)
181         latchEnabled.await(100, TimeUnit.MILLISECONDS)
182         runWithShellPermissionIdentity {
183             spm.removeSensorPrivacyListener(sensor, listener)
184         }
185     }
186 
187     @Test
188     @AppModeFull(reason = "Instant apps can't manage keyguard")
189     fun testCantChangeWhenLocked() {
190         setSensor(false)
191         assertFalse(isSensorPrivacyEnabled())
192         runWhileLocked {
193             setSensor(true)
194             assertFalse("State was changed while device is locked",
195                     isSensorPrivacyEnabled())
196         }
197 
198         setSensor(true)
199         assertTrue(isSensorPrivacyEnabled())
200         runWhileLocked {
201             setSensor(false)
202             assertTrue("State was changed while device is locked",
203                     isSensorPrivacyEnabled())
204         }
205     }
206 
207     fun unblockSensorWithDialogAndAssert() {
208         UiAutomatorUtils.waitFindObject(By.text(
209                 Pattern.compile("Unblock", Pattern.CASE_INSENSITIVE))).click()
210         eventually {
211             assertFalse(isSensorPrivacyEnabled())
212         }
213     }
214 
215     @Test
216     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
217     fun testOpNotRunningWhileSensorPrivacyEnabled() {
218         setSensor(false)
219         val before = System.currentTimeMillis()
220         startTestApp()
221         eventually {
222             assertOpRunning(true)
223         }
224         Thread.sleep(SENSOR_USE_TIME_MS)
225         setSensor(true)
226         eventually {
227             val after = System.currentTimeMillis()
228             assertOpRunning(false)
229             assertLastAccessTimeAndDuration(before, after)
230         }
231     }
232 
233     @Test
234     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
235     fun testOpStartsRunningAfterStartedWithSensoryPrivacyEnabled() {
236         setSensor(true)
237         startTestApp()
238         UiAutomatorUtils.waitFindObject(By.text(
239                 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE))).click()
240         assertOpRunning(false)
241         setSensor(false)
242         eventually {
243             assertOpRunning(true)
244         }
245     }
246 
247     @Test
248     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
249     fun testOpGetsRecordedAfterStartedWithSensorPrivacyEnabled() {
250         setSensor(true)
251         startTestApp()
252         UiAutomatorUtils.waitFindObject(By.text(
253                 Pattern.compile("Cancel", Pattern.CASE_INSENSITIVE))).click()
254         val before = System.currentTimeMillis()
255         setSensor(false)
256         eventually {
257             assertOpRunning(true)
258         }
259         setSensor(true)
260         eventually {
261             val after = System.currentTimeMillis()
262             assertLastAccessTimeAndDuration(before, after)
263         }
264     }
265 
266     @Test
267     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
268     fun testOpLastAccessUpdatesAfterToggleSensorPrivacy() {
269         setSensor(false)
270         val before = System.currentTimeMillis()
271         startTestApp()
272         eventually {
273             assertOpRunning(true)
274         }
275         Thread.sleep(SENSOR_USE_TIME_MS)
276         setSensor(true)
277         eventually {
278             val after = System.currentTimeMillis()
279             assertOpRunning(false)
280             assertLastAccessTimeAndDuration(before, after)
281         }
282 
283         val before2 = System.currentTimeMillis()
284         setSensor(false)
285         eventually {
286             assertOpRunning(true)
287         }
288         Thread.sleep(SENSOR_USE_TIME_MS)
289         setSensor(true)
290         eventually {
291             val after = System.currentTimeMillis()
292             assertOpRunning(false)
293             assertLastAccessTimeAndDuration(before2, after)
294         }
295     }
296 
297     @Test
298     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
299     fun testOpFinishedWhileToggleOn() {
300         setSensor(false)
301         startTestApp()
302         eventually {
303             assertOpRunning(true)
304         }
305         setSensor(true)
306         Thread.sleep(5000)
307         eventually {
308             assertOpRunning(false)
309         }
310         finishTestApp()
311         Thread.sleep(1000)
312         setSensor(false)
313         Thread.sleep(1000)
314         assertOpRunning(false)
315     }
316 
317     private fun startTestApp() {
318         val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
319                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
320                 .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
321         for (extra in extras) {
322             intent.putExtra(extra, true)
323         }
324         context.startActivity(intent)
325         // Wait for app to open
326         UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP))
327     }
328 
329     private fun finishTestApp() {
330         // instant apps can't broadcast to other instant apps; use the shell
331         runShellCommandOrThrow("am broadcast" +
332                 " --user ${context.userId}" +
333                 " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" +
334                 " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}")
335     }
336 
337     protected fun setSensor(enable: Boolean) {
338         runWithShellPermissionIdentity {
339             spm.setSensorPrivacy(OTHER, sensor, enable)
340         }
341     }
342 
343     private fun isSensorPrivacyEnabled(): Boolean {
344         return callWithShellPermissionIdentity {
345             spm.isSensorPrivacyEnabled(sensor)
346         }
347     }
348 
349     private fun getOpForSensor(sensor: Int): String? {
350         return when (sensor) {
351             CAMERA -> AppOpsManager.OPSTR_CAMERA
352             MICROPHONE -> AppOpsManager.OPSTR_RECORD_AUDIO
353             else -> null
354         }
355     }
356 
357     private fun getOpForPackage(): AppOpsManager.PackageOps {
358         return callWithShellPermissionIdentity {
359             val uid = try {
360                 packageManager.getPackageUid(PKG_NAME, 0)
361             } catch (e: PackageManager.NameNotFoundException) {
362                 // fail test
363                 assertNull(e)
364                 -1
365             }
366             val appOpsManager: AppOpsManager =
367                     context.getSystemService(AppOpsManager::class.java)!!
368             val pkgOps = appOpsManager.getOpsForPackage(uid, PKG_NAME, op)
369             assertFalse("expected non empty app op list", pkgOps.isEmpty())
370             pkgOps[0]
371         }
372     }
373 
374     private fun assertOpRunning(isRunning: Boolean) {
375         val pkgOp = getOpForPackage()
376         for (op in pkgOp.ops) {
377             for ((_, attrOp) in op.attributedOpEntries) {
378                 assertEquals("Unexpected op running state", isRunning, attrOp.isRunning)
379             }
380         }
381     }
382 
383     private fun assertLastAccessTimeAndDuration(before: Long, after: Long) {
384         val pkgOp = getOpForPackage()
385         for (op in pkgOp.ops) {
386             for ((_, attrOp) in op.attributedOpEntries) {
387                 val lastAccess = attrOp.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
388                 val lastDuration = attrOp.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
389                 assertTrue("lastAccess was $lastAccess, not between $before and $after",
390                         lastAccess in before..after)
391                 assertTrue("lastAccess had duration $lastDuration, greater than ${after - before}",
392                 lastDuration <= (after - before))
393             }
394         }
395     }
396 
397     fun runWhileLocked(r: () -> Unit) {
398         val km = context.getSystemService(KeyguardManager::class.java)!!
399         val pm = context.getSystemService(PowerManager::class.java)!!
400         val password = byteArrayOf(1, 2, 3, 4)
401         try {
402             runWithShellPermissionIdentity {
403                 km!!.setLock(KeyguardManager.PIN, password, KeyguardManager.PIN, null)
404             }
405             eventually {
406                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP)
407                 assertFalse("Device never slept.", pm.isInteractive)
408             }
409             eventually {
410                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP)
411                 assertTrue("Device never woke up.", pm.isInteractive)
412             }
413             eventually {
414                 assertTrue("Device isn't locked", km.isDeviceLocked)
415             }
416 
417             r.invoke()
418         } finally {
419             runWithShellPermissionIdentity {
420                 km!!.setLock(KeyguardManager.PIN, null, KeyguardManager.PIN, password)
421             }
422 
423             // Recycle the screen power in case the keyguard is stuck open
424             eventually {
425                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP)
426                 assertFalse("Device never slept.", pm.isInteractive)
427             }
428             eventually {
429                 uiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP)
430                 assertTrue("Device never woke up.", pm.isInteractive)
431             }
432 
433             eventually {
434                 assertFalse("Device isn't unlocked", km.isDeviceLocked)
435             }
436         }
437     }
438 }
439