1 /*
2  * Copyright (C) 2024 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.statusbar.policy
18 
19 import android.app.ActivityOptions
20 import android.app.IActivityManager
21 import android.app.Notification
22 import android.app.Notification.FLAG_FOREGROUND_SERVICE
23 import android.app.Notification.VISIBILITY_PRIVATE
24 import android.app.Notification.VISIBILITY_PUBLIC
25 import android.app.NotificationChannel
26 import android.app.NotificationManager.IMPORTANCE_HIGH
27 import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
28 import android.content.pm.PackageManager
29 import android.media.projection.MediaProjectionInfo
30 import android.media.projection.MediaProjectionManager
31 import android.os.Process
32 import android.os.UserHandle
33 import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
34 import android.platform.test.annotations.DisableFlags
35 import android.platform.test.annotations.EnableFlags
36 import android.platform.test.annotations.RequiresFlagsDisabled
37 import android.platform.test.annotations.RequiresFlagsEnabled
38 import android.platform.test.flag.junit.DeviceFlagsValueProvider
39 import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS
40 import android.telephony.TelephonyManager
41 import android.testing.AndroidTestingRunner
42 import android.testing.TestableLooper.RunWithLooper
43 import androidx.test.filters.SmallTest
44 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
45 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
46 import com.android.internal.util.FrameworkStatsLog
47 import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
48 import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
49 import com.android.systemui.SysuiTestCase
50 import com.android.systemui.log.logcatLogBuffer
51 import com.android.systemui.statusbar.RankingBuilder
52 import com.android.systemui.statusbar.notification.collection.NotificationEntry
53 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
54 import com.android.systemui.util.concurrency.FakeExecutor
55 import com.android.systemui.util.concurrency.mockExecutorHandler
56 import com.android.systemui.util.mockito.whenever
57 import com.android.systemui.util.mockito.withArgCaptor
58 import com.android.systemui.util.settings.FakeGlobalSettings
59 import com.android.systemui.util.time.FakeSystemClock
60 import org.junit.After
61 import org.junit.Assert.assertFalse
62 import org.junit.Assert.assertNotNull
63 import org.junit.Assert.assertTrue
64 import org.junit.Before
65 import org.junit.Rule
66 import org.junit.Test
67 import org.junit.runner.RunWith
68 import org.mockito.ArgumentMatchers.any
69 import org.mockito.ArgumentMatchers.anyLong
70 import org.mockito.ArgumentMatchers.anyString
71 import org.mockito.ArgumentMatchers.eq
72 import org.mockito.Mock
73 import org.mockito.Mockito.mock
74 import org.mockito.Mockito.times
75 import org.mockito.Mockito.verifyNoMoreInteractions
76 import org.mockito.Mockito.verifyZeroInteractions
77 import org.mockito.MockitoAnnotations
78 import org.mockito.MockitoSession
79 import org.mockito.quality.Strictness
80 
81 @SmallTest
82 @RunWith(AndroidTestingRunner::class)
83 @RunWithLooper
84 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
85 class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
86     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
87 
88     private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer())
89 
90     @Mock private lateinit var activityManager: IActivityManager
91     @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
92     @Mock private lateinit var packageManager: PackageManager
93     @Mock private lateinit var telephonyManager: TelephonyManager
94     @Mock private lateinit var listener1: Runnable
95     @Mock private lateinit var listener2: Runnable
96     @Mock private lateinit var listener3: Runnable
97 
98     private lateinit var staticMockSession: MockitoSession
99     private lateinit var executor: FakeExecutor
100     private lateinit var globalSettings: FakeGlobalSettings
101     private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
102     private lateinit var controller: SensitiveNotificationProtectionControllerImpl
103     private lateinit var mediaProjectionInfo: MediaProjectionInfo
104 
105     @Before
setUpnull106     fun setUp() {
107         staticMockSession =
108             mockitoSession()
109                 .mockStatic(FrameworkStatsLog::class.java)
110                 .strictness(Strictness.LENIENT)
111                 .startMocking()
112         allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners
113         MockitoAnnotations.initMocks(this)
114 
115         setShareFullScreen()
116         whenever(activityManager.bugreportWhitelistedPackages)
117             .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
118         whenever(
119                 packageManager.getPackageUidAsUser(
120                     TEST_PROJECTION_PACKAGE_NAME,
121                     UserHandle.CURRENT.identifier
122                 )
123             )
124             .thenReturn(TEST_PROJECTION_PACKAGE_UID)
125         whenever(
126                 packageManager.getPackageUidAsUser(
127                     BUGREPORT_PACKAGE_NAME,
128                     UserHandle.CURRENT.identifier
129                 )
130             )
131             .thenReturn(BUGREPORT_PACKAGE_UID)
132         // SystemUi context package name is exempt, but in test scenarios its
133         // com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to
134         // return the correct uid in this scenario
135         whenever(
136                 packageManager.getPackageUidAsUser(
137                     mContext.packageName,
138                     UserHandle.CURRENT.identifier
139                 )
140             )
141             .thenReturn(mContext.applicationInfo.uid)
142 
143         whenever(packageManager.checkPermission(anyString(), anyString()))
144             .thenReturn(PackageManager.PERMISSION_DENIED)
145 
146         whenever(telephonyManager.getEmergencyAssistancePackageName())
147             .thenReturn(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
148 
149         executor = FakeExecutor(FakeSystemClock())
150         globalSettings = FakeGlobalSettings()
151         controller =
152             SensitiveNotificationProtectionControllerImpl(
153                 mContext,
154                 globalSettings,
155                 mediaProjectionManager,
156                 activityManager,
157                 packageManager,
158                 telephonyManager,
159                 mockExecutorHandler(executor),
160                 executor,
161                 logger
162             )
163 
164         // Process pending work (getting global setting and list of exemptions)
165         executor.runAllReady()
166 
167         // Obtain useful MediaProjectionCallback
168         mediaProjectionCallback = withArgCaptor {
169             verify(mediaProjectionManager).addCallback(capture(), any())
170         }
171     }
172 
173     @After
tearDownnull174     fun tearDown() {
175         staticMockSession.finishMocking()
176     }
177 
178     @Test
init_registerMediaProjectionManagerCallbacknull179     fun init_registerMediaProjectionManagerCallback() {
180         assertNotNull(mediaProjectionCallback)
181     }
182 
183     @Test
registerSensitiveStateListener_singleListenernull184     fun registerSensitiveStateListener_singleListener() {
185         controller.registerSensitiveStateListener(listener1)
186 
187         mediaProjectionCallback.onStart(mediaProjectionInfo)
188         mediaProjectionCallback.onStop(mediaProjectionInfo)
189 
190         verify(listener1, times(2)).run()
191     }
192 
193     @Test
registerSensitiveStateListener_multipleListenersnull194     fun registerSensitiveStateListener_multipleListeners() {
195         controller.registerSensitiveStateListener(listener1)
196         controller.registerSensitiveStateListener(listener2)
197 
198         mediaProjectionCallback.onStart(mediaProjectionInfo)
199         mediaProjectionCallback.onStop(mediaProjectionInfo)
200 
201         verify(listener1, times(2)).run()
202         verify(listener2, times(2)).run()
203     }
204 
205     @Test
registerSensitiveStateListener_afterProjectionActivenull206     fun registerSensitiveStateListener_afterProjectionActive() {
207         mediaProjectionCallback.onStart(mediaProjectionInfo)
208 
209         controller.registerSensitiveStateListener(listener1)
210         verifyZeroInteractions(listener1)
211 
212         mediaProjectionCallback.onStop(mediaProjectionInfo)
213 
214         verify(listener1).run()
215     }
216 
217     @Test
unregisterSensitiveStateListener_singleListenernull218     fun unregisterSensitiveStateListener_singleListener() {
219         controller.registerSensitiveStateListener(listener1)
220 
221         mediaProjectionCallback.onStart(mediaProjectionInfo)
222         mediaProjectionCallback.onStop(mediaProjectionInfo)
223 
224         verify(listener1, times(2)).run()
225 
226         controller.unregisterSensitiveStateListener(listener1)
227 
228         mediaProjectionCallback.onStart(mediaProjectionInfo)
229         mediaProjectionCallback.onStop(mediaProjectionInfo)
230 
231         verifyNoMoreInteractions(listener1)
232     }
233 
234     @Test
unregisterSensitiveStateListener_multipleListenersnull235     fun unregisterSensitiveStateListener_multipleListeners() {
236         controller.registerSensitiveStateListener(listener1)
237         controller.registerSensitiveStateListener(listener2)
238         controller.registerSensitiveStateListener(listener3)
239 
240         mediaProjectionCallback.onStart(mediaProjectionInfo)
241         mediaProjectionCallback.onStop(mediaProjectionInfo)
242 
243         verify(listener1, times(2)).run()
244         verify(listener2, times(2)).run()
245         verify(listener3, times(2)).run()
246 
247         controller.unregisterSensitiveStateListener(listener1)
248         controller.unregisterSensitiveStateListener(listener2)
249 
250         mediaProjectionCallback.onStart(mediaProjectionInfo)
251         mediaProjectionCallback.onStop(mediaProjectionInfo)
252 
253         verifyNoMoreInteractions(listener1)
254         verifyNoMoreInteractions(listener2)
255         verify(listener3, times(4)).run()
256     }
257 
258     @Test
isSensitiveStateActive_projectionInactive_falsenull259     fun isSensitiveStateActive_projectionInactive_false() {
260         assertFalse(controller.isSensitiveStateActive)
261     }
262 
263     @Test
isSensitiveStateActive_projectionActive_truenull264     fun isSensitiveStateActive_projectionActive_true() {
265         mediaProjectionCallback.onStart(mediaProjectionInfo)
266 
267         assertTrue(controller.isSensitiveStateActive)
268     }
269 
270     @Test
isSensitiveStateActive_projectionInactiveAfterActive_falsenull271     fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
272         mediaProjectionCallback.onStart(mediaProjectionInfo)
273         mediaProjectionCallback.onStop(mediaProjectionInfo)
274 
275         assertFalse(controller.isSensitiveStateActive)
276     }
277 
278     @Test
isSensitiveStateActive_projectionActiveAfterInactive_truenull279     fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
280         mediaProjectionCallback.onStart(mediaProjectionInfo)
281         mediaProjectionCallback.onStop(mediaProjectionInfo)
282         mediaProjectionCallback.onStart(mediaProjectionInfo)
283 
284         assertTrue(controller.isSensitiveStateActive)
285     }
286 
287     @Test
isSensitiveStateActive_projectionActive_singleActivity_falsenull288     fun isSensitiveStateActive_projectionActive_singleActivity_false() {
289         setShareSingleApp()
290         mediaProjectionCallback.onStart(mediaProjectionInfo)
291 
292         assertFalse(controller.isSensitiveStateActive)
293     }
294 
295     @Test
isSensitiveStateActive_projectionActive_sysuiExempt_falsenull296     fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
297         // SystemUi context package name is exempt, but in test scenarios its
298         // com.android.systemui.tests so use that instead of hardcoding
299         setShareFullScreenViaSystemUi()
300         mediaProjectionCallback.onStart(mediaProjectionInfo)
301 
302         assertFalse(controller.isSensitiveStateActive)
303     }
304 
305     @Test
306     @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_truenull307     fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() {
308         whenever(
309                 packageManager.checkPermission(
310                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
311                     mediaProjectionInfo.packageName
312                 )
313             )
314             .thenReturn(PackageManager.PERMISSION_GRANTED)
315         mediaProjectionCallback.onStart(mediaProjectionInfo)
316 
317         assertTrue(controller.isSensitiveStateActive)
318     }
319 
320     @Test
321     @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
isSensitiveStateActive_projectionActive_permissionExempt_falsenull322     fun isSensitiveStateActive_projectionActive_permissionExempt_false() {
323         whenever(
324                 packageManager.checkPermission(
325                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
326                     mediaProjectionInfo.packageName
327                 )
328             )
329             .thenReturn(PackageManager.PERMISSION_GRANTED)
330         mediaProjectionCallback.onStart(mediaProjectionInfo)
331 
332         assertFalse(controller.isSensitiveStateActive)
333     }
334 
335     @Test
isSensitiveStateActive_projectionActive_bugReportHandlerExempt_falsenull336     fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
337         setShareFullScreenViaBugReportHandler()
338         mediaProjectionCallback.onStart(mediaProjectionInfo)
339 
340         assertFalse(controller.isSensitiveStateActive)
341     }
342 
343     @Test
isSensitiveStateActive_projectionActive_disabledViaDevOption_falsenull344     fun isSensitiveStateActive_projectionActive_disabledViaDevOption_false() {
345         setDisabledViaDeveloperOption()
346         mediaProjectionCallback.onStart(mediaProjectionInfo)
347 
348         assertFalse(controller.isSensitiveStateActive)
349     }
350 
351     @Test
shouldProtectNotification_projectionInactive_falsenull352     fun shouldProtectNotification_projectionInactive_false() {
353         val notificationEntry = mock(NotificationEntry::class.java)
354 
355         assertFalse(controller.shouldProtectNotification(notificationEntry))
356     }
357 
358     @Test
shouldProtectNotification_projectionActive_singleActivity_falsenull359     fun shouldProtectNotification_projectionActive_singleActivity_false() {
360         setShareSingleApp()
361         mediaProjectionCallback.onStart(mediaProjectionInfo)
362 
363         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
364 
365         assertFalse(controller.shouldProtectNotification(notificationEntry))
366     }
367 
368     @Test
shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_falsenull369     fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
370         mediaProjectionCallback.onStart(mediaProjectionInfo)
371 
372         val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
373 
374         assertFalse(controller.shouldProtectNotification(notificationEntry))
375     }
376 
377     @Test
shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_truenull378     fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
379         mediaProjectionCallback.onStart(mediaProjectionInfo)
380 
381         val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
382 
383         assertTrue(controller.shouldProtectNotification(notificationEntry))
384     }
385 
386     @Test
shouldProtectNotification_projectionActive_notFgsNotification_truenull387     fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
388         mediaProjectionCallback.onStart(mediaProjectionInfo)
389 
390         val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
391 
392         assertTrue(controller.shouldProtectNotification(notificationEntry))
393     }
394 
395     @Test
396     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_truenull397     fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() {
398         mediaProjectionCallback.onStart(mediaProjectionInfo)
399 
400         val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
401 
402         assertTrue(controller.shouldProtectNotification(notificationEntry))
403     }
404 
405     @Test
406     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromCoreApp_falsenull407     fun shouldProtectNotification_projectionActive_isFromCoreApp_false() {
408         mediaProjectionCallback.onStart(mediaProjectionInfo)
409 
410         val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
411 
412         assertFalse(controller.shouldProtectNotification(notificationEntry))
413     }
414 
415     @Test
416     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_truenull417     fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_true() {
418         mediaProjectionCallback.onStart(mediaProjectionInfo)
419 
420         val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
421 
422         assertTrue(controller.shouldProtectNotification(notificationEntry))
423     }
424 
425     @Test
426     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromEmergencyPackage_falsenull427     fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_false() {
428         mediaProjectionCallback.onStart(mediaProjectionInfo)
429 
430         val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
431 
432         assertFalse(controller.shouldProtectNotification(notificationEntry))
433     }
434 
435     @Test
shouldProtectNotification_projectionActive_sysuiExempt_falsenull436     fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
437         // SystemUi context package name is exempt, but in test scenarios its
438         // com.android.systemui.tests so use that instead of hardcoding
439         setShareFullScreenViaSystemUi()
440         mediaProjectionCallback.onStart(mediaProjectionInfo)
441 
442         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
443 
444         assertFalse(controller.shouldProtectNotification(notificationEntry))
445     }
446 
447     @Test
448     @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_truenull449     fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() {
450         whenever(
451                 packageManager.checkPermission(
452                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
453                     mediaProjectionInfo.packageName
454                 )
455             )
456             .thenReturn(PackageManager.PERMISSION_GRANTED)
457         mediaProjectionCallback.onStart(mediaProjectionInfo)
458 
459         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
460 
461         assertTrue(controller.shouldProtectNotification(notificationEntry))
462     }
463 
464     @Test
465     @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
shouldProtectNotification_projectionActive_permissionExempt_falsenull466     fun shouldProtectNotification_projectionActive_permissionExempt_false() {
467         whenever(
468                 packageManager.checkPermission(
469                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
470                     mediaProjectionInfo.packageName
471                 )
472             )
473             .thenReturn(PackageManager.PERMISSION_GRANTED)
474         mediaProjectionCallback.onStart(mediaProjectionInfo)
475 
476         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
477 
478         assertFalse(controller.shouldProtectNotification(notificationEntry))
479     }
480 
481     @Test
shouldProtectNotification_projectionActive_bugReportHandlerExempt_falsenull482     fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
483         setShareFullScreenViaBugReportHandler()
484         mediaProjectionCallback.onStart(mediaProjectionInfo)
485 
486         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
487 
488         assertFalse(controller.shouldProtectNotification(notificationEntry))
489     }
490 
491     @Test
shouldProtectNotification_projectionActive_disabledViaDevOption_falsenull492     fun shouldProtectNotification_projectionActive_disabledViaDevOption_false() {
493         setDisabledViaDeveloperOption()
494         mediaProjectionCallback.onStart(mediaProjectionInfo)
495 
496         val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
497 
498         assertFalse(controller.shouldProtectNotification(notificationEntry))
499     }
500 
501     @Test
shouldProtectNotification_projectionActive_publicNotification_falsenull502     fun shouldProtectNotification_projectionActive_publicNotification_false() {
503         mediaProjectionCallback.onStart(mediaProjectionInfo)
504 
505         // App marked notification visibility as public
506         val notificationEntry = setupPublicNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
507 
508         assertFalse(controller.shouldProtectNotification(notificationEntry))
509     }
510 
511     @Test
shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_truenull512     fun shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_true() {
513         mediaProjectionCallback.onStart(mediaProjectionInfo)
514 
515         val notificationEntry =
516             setupPublicNotificationEntryWithUserOverriddenChannel(TEST_PROJECTION_PACKAGE_NAME)
517 
518         assertTrue(controller.shouldProtectNotification(notificationEntry))
519     }
520 
521     @Test
logSensitiveContentProtectionSessionnull522     fun logSensitiveContentProtectionSession() {
523         mediaProjectionCallback.onStart(mediaProjectionInfo)
524 
525         verify {
526             FrameworkStatsLog.write(
527                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
528                 anyLong(),
529                 eq(TEST_PROJECTION_PACKAGE_UID),
530                 eq(false),
531                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
532                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
533             )
534         }
535 
536         mediaProjectionCallback.onStop(mediaProjectionInfo)
537 
538         verify {
539             FrameworkStatsLog.write(
540                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
541                 anyLong(),
542                 eq(TEST_PROJECTION_PACKAGE_UID),
543                 eq(false),
544                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
545                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
546             )
547         }
548     }
549 
550     @Test
logSensitiveContentProtectionSession_exemptViaShareSingleAppnull551     fun logSensitiveContentProtectionSession_exemptViaShareSingleApp() {
552         setShareSingleApp()
553 
554         mediaProjectionCallback.onStart(mediaProjectionInfo)
555 
556         verify {
557             FrameworkStatsLog.write(
558                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
559                 anyLong(),
560                 eq(TEST_PROJECTION_PACKAGE_UID),
561                 eq(true),
562                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
563                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
564             )
565         }
566 
567         mediaProjectionCallback.onStop(mediaProjectionInfo)
568 
569         verify {
570             FrameworkStatsLog.write(
571                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
572                 anyLong(),
573                 eq(TEST_PROJECTION_PACKAGE_UID),
574                 eq(true),
575                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
576                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
577             )
578         }
579     }
580 
581     @Test
logSensitiveContentProtectionSession_exemptViaDeveloperOptionnull582     fun logSensitiveContentProtectionSession_exemptViaDeveloperOption() {
583         setDisabledViaDeveloperOption()
584 
585         mediaProjectionCallback.onStart(mediaProjectionInfo)
586 
587         verify {
588             FrameworkStatsLog.write(
589                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
590                 anyLong(),
591                 eq(TEST_PROJECTION_PACKAGE_UID),
592                 eq(true),
593                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
594                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
595             )
596         }
597 
598         mediaProjectionCallback.onStop(mediaProjectionInfo)
599 
600         verify {
601             FrameworkStatsLog.write(
602                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
603                 anyLong(),
604                 eq(TEST_PROJECTION_PACKAGE_UID),
605                 eq(true),
606                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
607                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
608             )
609         }
610     }
611 
612     @Test
logSensitiveContentProtectionSession_exemptViaSystemUinull613     fun logSensitiveContentProtectionSession_exemptViaSystemUi() {
614         // SystemUi context package name is exempt, but in test scenarios its
615         // com.android.systemui.tests so use that instead of hardcoding
616         setShareFullScreenViaSystemUi()
617 
618         mediaProjectionCallback.onStart(mediaProjectionInfo)
619 
620         verify {
621             FrameworkStatsLog.write(
622                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
623                 anyLong(),
624                 eq(mContext.applicationInfo.uid),
625                 eq(true),
626                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
627                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
628             )
629         }
630 
631         mediaProjectionCallback.onStop(mediaProjectionInfo)
632 
633         verify {
634             FrameworkStatsLog.write(
635                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
636                 anyLong(),
637                 eq(mContext.applicationInfo.uid),
638                 eq(true),
639                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
640                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
641             )
642         }
643     }
644 
645     @Test
logSensitiveContentProtectionSession_exemptViaBugReportHandlernull646     fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() {
647         // Setup exempt via bugreport handler
648         setShareFullScreenViaBugReportHandler()
649         mediaProjectionCallback.onStart(mediaProjectionInfo)
650 
651         verify {
652             FrameworkStatsLog.write(
653                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
654                 anyLong(),
655                 eq(BUGREPORT_PACKAGE_UID),
656                 eq(true),
657                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
658                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
659             )
660         }
661 
662         mediaProjectionCallback.onStop(mediaProjectionInfo)
663 
664         verify {
665             FrameworkStatsLog.write(
666                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
667                 anyLong(),
668                 eq(BUGREPORT_PACKAGE_UID),
669                 eq(true),
670                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
671                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
672             )
673         }
674     }
675 
setDisabledViaDeveloperOptionnull676     private fun setDisabledViaDeveloperOption() {
677         globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1)
678 
679         // Process pending work that gets current developer option global setting
680         executor.runAllReady()
681     }
682 
setShareFullScreennull683     private fun setShareFullScreen() {
684         setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true)
685     }
686 
setShareFullScreenViaBugReportHandlernull687     private fun setShareFullScreenViaBugReportHandler() {
688         setShareScreen(BUGREPORT_PACKAGE_NAME, true)
689     }
690 
setShareFullScreenViaSystemUinull691     private fun setShareFullScreenViaSystemUi() {
692         // SystemUi context package name is exempt, but in test scenarios its
693         // com.android.systemui.tests so use that instead of hardcoding
694         setShareScreen(mContext.packageName, true)
695     }
696 
setShareSingleAppnull697     private fun setShareSingleApp() {
698         setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false)
699     }
700 
setShareScreennull701     private fun setShareScreen(packageName: String, fullScreen: Boolean) {
702         val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie()
703         mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie)
704     }
705 
setupNotificationEntrynull706     private fun setupNotificationEntry(
707         packageName: String,
708         isFgs: Boolean = false,
709         isCoreApp: Boolean = false,
710         overrideVisibility: Boolean = false,
711         overrideChannelVisibility: Boolean = false,
712     ): NotificationEntry {
713         val notification = Notification()
714         if (isFgs) {
715             notification.flags = notification.flags or FLAG_FOREGROUND_SERVICE
716         }
717         if (overrideVisibility) {
718             // Developer has marked notification as public
719             notification.visibility = VISIBILITY_PUBLIC
720         }
721         val notificationEntryBuilder =
722             NotificationEntryBuilder().setNotification(notification).setPkg(packageName)
723         if (isCoreApp) {
724             notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10)
725         } else {
726             notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10)
727         }
728         val notificationEntry = notificationEntryBuilder.build()
729         val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH)
730         if (overrideChannelVisibility) {
731             // User doesn't allow private notifications at the channel level
732             channel.lockscreenVisibility = VISIBILITY_PRIVATE
733         }
734         notificationEntry.setRanking(
735             RankingBuilder(notificationEntry.ranking)
736                 .setChannel(channel)
737                 .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
738                 .build()
739         )
740         return notificationEntry
741     }
742 
setupFgsNotificationEntrynull743     private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
744         return setupNotificationEntry(packageName, isFgs = true)
745     }
746 
setupCoreAppNotificationEntrynull747     private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry {
748         return setupNotificationEntry(packageName, isCoreApp = true)
749     }
750 
setupPublicNotificationEntrynull751     private fun setupPublicNotificationEntry(packageName: String): NotificationEntry {
752         return setupNotificationEntry(packageName, overrideVisibility = true)
753     }
754 
setupPublicNotificationEntryWithUserOverriddenChannelnull755     private fun setupPublicNotificationEntryWithUserOverriddenChannel(
756         packageName: String
757     ): NotificationEntry {
758         return setupNotificationEntry(
759             packageName,
760             overrideVisibility = true,
761             overrideChannelVisibility = true
762         )
763     }
764 
765     companion object {
766         private const val TEST_PROJECTION_PACKAGE_UID = 23
767         private const val BUGREPORT_PACKAGE_UID = 24
768         private const val TEST_PROJECTION_PACKAGE_NAME =
769             "com.android.systemui.statusbar.policy.projectionpackage"
770         private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
771         private const val EMERGENCY_ASSISTANCE_PACKAGE_NAME = "com.android.test.emergencyassistance"
772         private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler"
773     }
774 }
775