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.MockitoAnnotations
77 import org.mockito.MockitoSession
78 import org.mockito.quality.Strictness
79 
80 @SmallTest
81 @RunWith(AndroidTestingRunner::class)
82 @RunWithLooper
83 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
84 class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
85     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
86 
87     private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer())
88 
89     @Mock private lateinit var activityManager: IActivityManager
90     @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
91     @Mock private lateinit var packageManager: PackageManager
92     @Mock private lateinit var telephonyManager: TelephonyManager
93     @Mock private lateinit var listener1: Runnable
94     @Mock private lateinit var listener2: Runnable
95     @Mock private lateinit var listener3: Runnable
96 
97     private lateinit var staticMockSession: MockitoSession
98     private lateinit var executor: FakeExecutor
99     private lateinit var globalSettings: FakeGlobalSettings
100     private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
101     private lateinit var controller: SensitiveNotificationProtectionControllerImpl
102     private lateinit var mediaProjectionInfo: MediaProjectionInfo
103 
104     @Before
setUpnull105     fun setUp() {
106         staticMockSession =
107             mockitoSession()
108                 .mockStatic(FrameworkStatsLog::class.java)
109                 .strictness(Strictness.LENIENT)
110                 .startMocking()
111         allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners
112         MockitoAnnotations.initMocks(this)
113 
114         setShareFullScreen()
115         whenever(activityManager.bugreportWhitelistedPackages)
116             .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
117         whenever(
118                 packageManager.getPackageUidAsUser(
119                     TEST_PROJECTION_PACKAGE_NAME,
120                     UserHandle.CURRENT.identifier
121                 )
122             )
123             .thenReturn(TEST_PROJECTION_PACKAGE_UID)
124         whenever(
125                 packageManager.getPackageUidAsUser(
126                     BUGREPORT_PACKAGE_NAME,
127                     UserHandle.CURRENT.identifier
128                 )
129             )
130             .thenReturn(BUGREPORT_PACKAGE_UID)
131         // SystemUi context package name is exempt, but in test scenarios its
132         // com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to
133         // return the correct uid in this scenario
134         whenever(
135                 packageManager.getPackageUidAsUser(
136                     mContext.packageName,
137                     UserHandle.CURRENT.identifier
138                 )
139             )
140             .thenReturn(mContext.applicationInfo.uid)
141 
142         whenever(packageManager.checkPermission(anyString(), anyString()))
143             .thenReturn(PackageManager.PERMISSION_DENIED)
144 
145         whenever(telephonyManager.getEmergencyAssistancePackageName())
146             .thenReturn(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
147 
148         executor = FakeExecutor(FakeSystemClock())
149         globalSettings = FakeGlobalSettings()
150         controller =
151             SensitiveNotificationProtectionControllerImpl(
152                 mContext,
153                 globalSettings,
154                 mediaProjectionManager,
155                 activityManager,
156                 packageManager,
157                 telephonyManager,
158                 mockExecutorHandler(executor),
159                 executor,
160                 logger
161             )
162 
163         // Process pending work (getting global setting and list of exemptions)
164         executor.runAllReady()
165 
166         // Obtain useful MediaProjectionCallback
167         mediaProjectionCallback = withArgCaptor {
168             verify(mediaProjectionManager).addCallback(capture(), any())
169         }
170     }
171 
172     @After
tearDownnull173     fun tearDown() {
174         staticMockSession.finishMocking()
175     }
176 
177     @Test
init_registerMediaProjectionManagerCallbacknull178     fun init_registerMediaProjectionManagerCallback() {
179         assertNotNull(mediaProjectionCallback)
180     }
181 
182     @Test
registerSensitiveStateListener_singleListenernull183     fun registerSensitiveStateListener_singleListener() {
184         controller.registerSensitiveStateListener(listener1)
185 
186         mediaProjectionCallback.onStart(mediaProjectionInfo)
187         mediaProjectionCallback.onStop(mediaProjectionInfo)
188 
189         verify(listener1, times(2)).run()
190     }
191 
192     @Test
registerSensitiveStateListener_multipleListenersnull193     fun registerSensitiveStateListener_multipleListeners() {
194         controller.registerSensitiveStateListener(listener1)
195         controller.registerSensitiveStateListener(listener2)
196 
197         mediaProjectionCallback.onStart(mediaProjectionInfo)
198         mediaProjectionCallback.onStop(mediaProjectionInfo)
199 
200         verify(listener1, times(2)).run()
201         verify(listener2, times(2)).run()
202     }
203 
204     @Test
registerSensitiveStateListener_afterProjectionActivenull205     fun registerSensitiveStateListener_afterProjectionActive() {
206         mediaProjectionCallback.onStart(mediaProjectionInfo)
207 
208         controller.registerSensitiveStateListener(listener1)
209         verifyNoMoreInteractions(listener1)
210 
211         mediaProjectionCallback.onStop(mediaProjectionInfo)
212 
213         verify(listener1).run()
214     }
215 
216     @Test
unregisterSensitiveStateListener_singleListenernull217     fun unregisterSensitiveStateListener_singleListener() {
218         controller.registerSensitiveStateListener(listener1)
219 
220         mediaProjectionCallback.onStart(mediaProjectionInfo)
221         mediaProjectionCallback.onStop(mediaProjectionInfo)
222 
223         verify(listener1, times(2)).run()
224 
225         controller.unregisterSensitiveStateListener(listener1)
226 
227         mediaProjectionCallback.onStart(mediaProjectionInfo)
228         mediaProjectionCallback.onStop(mediaProjectionInfo)
229 
230         verifyNoMoreInteractions(listener1)
231     }
232 
233     @Test
unregisterSensitiveStateListener_multipleListenersnull234     fun unregisterSensitiveStateListener_multipleListeners() {
235         controller.registerSensitiveStateListener(listener1)
236         controller.registerSensitiveStateListener(listener2)
237         controller.registerSensitiveStateListener(listener3)
238 
239         mediaProjectionCallback.onStart(mediaProjectionInfo)
240         mediaProjectionCallback.onStop(mediaProjectionInfo)
241 
242         verify(listener1, times(2)).run()
243         verify(listener2, times(2)).run()
244         verify(listener3, times(2)).run()
245 
246         controller.unregisterSensitiveStateListener(listener1)
247         controller.unregisterSensitiveStateListener(listener2)
248 
249         mediaProjectionCallback.onStart(mediaProjectionInfo)
250         mediaProjectionCallback.onStop(mediaProjectionInfo)
251 
252         verifyNoMoreInteractions(listener1)
253         verifyNoMoreInteractions(listener2)
254         verify(listener3, times(4)).run()
255     }
256 
257     @Test
isSensitiveStateActive_projectionInactive_falsenull258     fun isSensitiveStateActive_projectionInactive_false() {
259         assertFalse(controller.isSensitiveStateActive)
260     }
261 
262     @Test
isSensitiveStateActive_projectionActive_truenull263     fun isSensitiveStateActive_projectionActive_true() {
264         mediaProjectionCallback.onStart(mediaProjectionInfo)
265 
266         assertTrue(controller.isSensitiveStateActive)
267     }
268 
269     @Test
isSensitiveStateActive_projectionInactiveAfterActive_falsenull270     fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
271         mediaProjectionCallback.onStart(mediaProjectionInfo)
272         mediaProjectionCallback.onStop(mediaProjectionInfo)
273 
274         assertFalse(controller.isSensitiveStateActive)
275     }
276 
277     @Test
isSensitiveStateActive_projectionActiveAfterInactive_truenull278     fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
279         mediaProjectionCallback.onStart(mediaProjectionInfo)
280         mediaProjectionCallback.onStop(mediaProjectionInfo)
281         mediaProjectionCallback.onStart(mediaProjectionInfo)
282 
283         assertTrue(controller.isSensitiveStateActive)
284     }
285 
286     @Test
isSensitiveStateActive_projectionActive_singleActivity_falsenull287     fun isSensitiveStateActive_projectionActive_singleActivity_false() {
288         setShareSingleApp()
289         mediaProjectionCallback.onStart(mediaProjectionInfo)
290 
291         assertFalse(controller.isSensitiveStateActive)
292     }
293 
294     @Test
isSensitiveStateActive_projectionActive_sysuiExempt_falsenull295     fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
296         // SystemUi context package name is exempt, but in test scenarios its
297         // com.android.systemui.tests so use that instead of hardcoding
298         setShareFullScreenViaSystemUi()
299         mediaProjectionCallback.onStart(mediaProjectionInfo)
300 
301         assertFalse(controller.isSensitiveStateActive)
302     }
303 
304     @Test
305     @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_truenull306     fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() {
307         whenever(
308                 packageManager.checkPermission(
309                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
310                     mediaProjectionInfo.packageName
311                 )
312             )
313             .thenReturn(PackageManager.PERMISSION_GRANTED)
314         mediaProjectionCallback.onStart(mediaProjectionInfo)
315 
316         assertTrue(controller.isSensitiveStateActive)
317     }
318 
319     @Test
320     @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
isSensitiveStateActive_projectionActive_permissionExempt_falsenull321     fun isSensitiveStateActive_projectionActive_permissionExempt_false() {
322         whenever(
323                 packageManager.checkPermission(
324                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
325                     mediaProjectionInfo.packageName
326                 )
327             )
328             .thenReturn(PackageManager.PERMISSION_GRANTED)
329         mediaProjectionCallback.onStart(mediaProjectionInfo)
330 
331         assertFalse(controller.isSensitiveStateActive)
332     }
333 
334     @Test
isSensitiveStateActive_projectionActive_bugReportHandlerExempt_falsenull335     fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
336         setShareFullScreenViaBugReportHandler()
337         mediaProjectionCallback.onStart(mediaProjectionInfo)
338 
339         assertFalse(controller.isSensitiveStateActive)
340     }
341 
342     @Test
isSensitiveStateActive_projectionActive_disabledViaDevOption_falsenull343     fun isSensitiveStateActive_projectionActive_disabledViaDevOption_false() {
344         setDisabledViaDeveloperOption()
345         mediaProjectionCallback.onStart(mediaProjectionInfo)
346 
347         assertFalse(controller.isSensitiveStateActive)
348     }
349 
350     @Test
shouldProtectNotification_projectionInactive_falsenull351     fun shouldProtectNotification_projectionInactive_false() {
352         val notificationEntry = mock(NotificationEntry::class.java)
353 
354         assertFalse(controller.shouldProtectNotification(notificationEntry))
355     }
356 
357     @Test
shouldProtectNotification_projectionActive_singleActivity_falsenull358     fun shouldProtectNotification_projectionActive_singleActivity_false() {
359         setShareSingleApp()
360         mediaProjectionCallback.onStart(mediaProjectionInfo)
361 
362         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
363 
364         assertFalse(controller.shouldProtectNotification(notificationEntry))
365     }
366 
367     @Test
shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_falsenull368     fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
369         mediaProjectionCallback.onStart(mediaProjectionInfo)
370 
371         val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
372 
373         assertFalse(controller.shouldProtectNotification(notificationEntry))
374     }
375 
376     @Test
shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_truenull377     fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
378         mediaProjectionCallback.onStart(mediaProjectionInfo)
379 
380         val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
381 
382         assertTrue(controller.shouldProtectNotification(notificationEntry))
383     }
384 
385     @Test
shouldProtectNotification_projectionActive_notFgsNotification_truenull386     fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
387         mediaProjectionCallback.onStart(mediaProjectionInfo)
388 
389         val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
390 
391         assertTrue(controller.shouldProtectNotification(notificationEntry))
392     }
393 
394     @Test
395     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_truenull396     fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() {
397         mediaProjectionCallback.onStart(mediaProjectionInfo)
398 
399         val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
400 
401         assertTrue(controller.shouldProtectNotification(notificationEntry))
402     }
403 
404     @Test
405     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromCoreApp_falsenull406     fun shouldProtectNotification_projectionActive_isFromCoreApp_false() {
407         mediaProjectionCallback.onStart(mediaProjectionInfo)
408 
409         val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
410 
411         assertFalse(controller.shouldProtectNotification(notificationEntry))
412     }
413 
414     @Test
415     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_truenull416     fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_true() {
417         mediaProjectionCallback.onStart(mediaProjectionInfo)
418 
419         val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
420 
421         assertTrue(controller.shouldProtectNotification(notificationEntry))
422     }
423 
424     @Test
425     @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
shouldProtectNotification_projectionActive_isFromEmergencyPackage_falsenull426     fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_false() {
427         mediaProjectionCallback.onStart(mediaProjectionInfo)
428 
429         val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME)
430 
431         assertFalse(controller.shouldProtectNotification(notificationEntry))
432     }
433 
434     @Test
shouldProtectNotification_projectionActive_sysuiExempt_falsenull435     fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
436         // SystemUi context package name is exempt, but in test scenarios its
437         // com.android.systemui.tests so use that instead of hardcoding
438         setShareFullScreenViaSystemUi()
439         mediaProjectionCallback.onStart(mediaProjectionInfo)
440 
441         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
442 
443         assertFalse(controller.shouldProtectNotification(notificationEntry))
444     }
445 
446     @Test
447     @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_truenull448     fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() {
449         whenever(
450                 packageManager.checkPermission(
451                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
452                     mediaProjectionInfo.packageName
453                 )
454             )
455             .thenReturn(PackageManager.PERMISSION_GRANTED)
456         mediaProjectionCallback.onStart(mediaProjectionInfo)
457 
458         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
459 
460         assertTrue(controller.shouldProtectNotification(notificationEntry))
461     }
462 
463     @Test
464     @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
shouldProtectNotification_projectionActive_permissionExempt_falsenull465     fun shouldProtectNotification_projectionActive_permissionExempt_false() {
466         whenever(
467                 packageManager.checkPermission(
468                     android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
469                     mediaProjectionInfo.packageName
470                 )
471             )
472             .thenReturn(PackageManager.PERMISSION_GRANTED)
473         mediaProjectionCallback.onStart(mediaProjectionInfo)
474 
475         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
476 
477         assertFalse(controller.shouldProtectNotification(notificationEntry))
478     }
479 
480     @Test
shouldProtectNotification_projectionActive_bugReportHandlerExempt_falsenull481     fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
482         setShareFullScreenViaBugReportHandler()
483         mediaProjectionCallback.onStart(mediaProjectionInfo)
484 
485         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
486 
487         assertFalse(controller.shouldProtectNotification(notificationEntry))
488     }
489 
490     @Test
shouldProtectNotification_projectionActive_disabledViaDevOption_falsenull491     fun shouldProtectNotification_projectionActive_disabledViaDevOption_false() {
492         setDisabledViaDeveloperOption()
493         mediaProjectionCallback.onStart(mediaProjectionInfo)
494 
495         val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
496 
497         assertFalse(controller.shouldProtectNotification(notificationEntry))
498     }
499 
500     @Test
shouldProtectNotification_projectionActive_publicNotification_falsenull501     fun shouldProtectNotification_projectionActive_publicNotification_false() {
502         mediaProjectionCallback.onStart(mediaProjectionInfo)
503 
504         // App marked notification visibility as public
505         val notificationEntry = setupPublicNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
506 
507         assertFalse(controller.shouldProtectNotification(notificationEntry))
508     }
509 
510     @Test
shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_truenull511     fun shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_true() {
512         mediaProjectionCallback.onStart(mediaProjectionInfo)
513 
514         val notificationEntry =
515             setupPublicNotificationEntryWithUserOverriddenChannel(TEST_PROJECTION_PACKAGE_NAME)
516 
517         assertTrue(controller.shouldProtectNotification(notificationEntry))
518     }
519 
520     @Test
logSensitiveContentProtectionSessionnull521     fun logSensitiveContentProtectionSession() {
522         mediaProjectionCallback.onStart(mediaProjectionInfo)
523 
524         verify {
525             FrameworkStatsLog.write(
526                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
527                 anyLong(),
528                 eq(TEST_PROJECTION_PACKAGE_UID),
529                 eq(false),
530                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
531                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
532             )
533         }
534 
535         mediaProjectionCallback.onStop(mediaProjectionInfo)
536 
537         verify {
538             FrameworkStatsLog.write(
539                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
540                 anyLong(),
541                 eq(TEST_PROJECTION_PACKAGE_UID),
542                 eq(false),
543                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
544                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
545             )
546         }
547     }
548 
549     @Test
logSensitiveContentProtectionSession_exemptViaShareSingleAppnull550     fun logSensitiveContentProtectionSession_exemptViaShareSingleApp() {
551         setShareSingleApp()
552 
553         mediaProjectionCallback.onStart(mediaProjectionInfo)
554 
555         verify {
556             FrameworkStatsLog.write(
557                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
558                 anyLong(),
559                 eq(TEST_PROJECTION_PACKAGE_UID),
560                 eq(true),
561                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
562                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
563             )
564         }
565 
566         mediaProjectionCallback.onStop(mediaProjectionInfo)
567 
568         verify {
569             FrameworkStatsLog.write(
570                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
571                 anyLong(),
572                 eq(TEST_PROJECTION_PACKAGE_UID),
573                 eq(true),
574                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
575                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
576             )
577         }
578     }
579 
580     @Test
logSensitiveContentProtectionSession_exemptViaDeveloperOptionnull581     fun logSensitiveContentProtectionSession_exemptViaDeveloperOption() {
582         setDisabledViaDeveloperOption()
583 
584         mediaProjectionCallback.onStart(mediaProjectionInfo)
585 
586         verify {
587             FrameworkStatsLog.write(
588                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
589                 anyLong(),
590                 eq(TEST_PROJECTION_PACKAGE_UID),
591                 eq(true),
592                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
593                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
594             )
595         }
596 
597         mediaProjectionCallback.onStop(mediaProjectionInfo)
598 
599         verify {
600             FrameworkStatsLog.write(
601                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
602                 anyLong(),
603                 eq(TEST_PROJECTION_PACKAGE_UID),
604                 eq(true),
605                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
606                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
607             )
608         }
609     }
610 
611     @Test
logSensitiveContentProtectionSession_exemptViaSystemUinull612     fun logSensitiveContentProtectionSession_exemptViaSystemUi() {
613         // SystemUi context package name is exempt, but in test scenarios its
614         // com.android.systemui.tests so use that instead of hardcoding
615         setShareFullScreenViaSystemUi()
616 
617         mediaProjectionCallback.onStart(mediaProjectionInfo)
618 
619         verify {
620             FrameworkStatsLog.write(
621                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
622                 anyLong(),
623                 eq(mContext.applicationInfo.uid),
624                 eq(true),
625                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
626                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
627             )
628         }
629 
630         mediaProjectionCallback.onStop(mediaProjectionInfo)
631 
632         verify {
633             FrameworkStatsLog.write(
634                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
635                 anyLong(),
636                 eq(mContext.applicationInfo.uid),
637                 eq(true),
638                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
639                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
640             )
641         }
642     }
643 
644     @Test
logSensitiveContentProtectionSession_exemptViaBugReportHandlernull645     fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() {
646         // Setup exempt via bugreport handler
647         setShareFullScreenViaBugReportHandler()
648         mediaProjectionCallback.onStart(mediaProjectionInfo)
649 
650         verify {
651             FrameworkStatsLog.write(
652                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
653                 anyLong(),
654                 eq(BUGREPORT_PACKAGE_UID),
655                 eq(true),
656                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
657                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
658             )
659         }
660 
661         mediaProjectionCallback.onStop(mediaProjectionInfo)
662 
663         verify {
664             FrameworkStatsLog.write(
665                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
666                 anyLong(),
667                 eq(BUGREPORT_PACKAGE_UID),
668                 eq(true),
669                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
670                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
671             )
672         }
673     }
674 
setDisabledViaDeveloperOptionnull675     private fun setDisabledViaDeveloperOption() {
676         globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1)
677 
678         // Process pending work that gets current developer option global setting
679         executor.runAllReady()
680     }
681 
setShareFullScreennull682     private fun setShareFullScreen() {
683         setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true)
684     }
685 
setShareFullScreenViaBugReportHandlernull686     private fun setShareFullScreenViaBugReportHandler() {
687         setShareScreen(BUGREPORT_PACKAGE_NAME, true)
688     }
689 
setShareFullScreenViaSystemUinull690     private fun setShareFullScreenViaSystemUi() {
691         // SystemUi context package name is exempt, but in test scenarios its
692         // com.android.systemui.tests so use that instead of hardcoding
693         setShareScreen(mContext.packageName, true)
694     }
695 
setShareSingleAppnull696     private fun setShareSingleApp() {
697         setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false)
698     }
699 
setShareScreennull700     private fun setShareScreen(packageName: String, fullScreen: Boolean) {
701         val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie()
702         mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie)
703     }
704 
setupNotificationEntrynull705     private fun setupNotificationEntry(
706         packageName: String,
707         isFgs: Boolean = false,
708         isCoreApp: Boolean = false,
709         overrideVisibility: Boolean = false,
710         overrideChannelVisibility: Boolean = false,
711     ): NotificationEntry {
712         val notification = Notification()
713         if (isFgs) {
714             notification.flags = notification.flags or FLAG_FOREGROUND_SERVICE
715         }
716         if (overrideVisibility) {
717             // Developer has marked notification as public
718             notification.visibility = VISIBILITY_PUBLIC
719         }
720         val notificationEntryBuilder =
721             NotificationEntryBuilder().setNotification(notification).setPkg(packageName)
722         if (isCoreApp) {
723             notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10)
724         } else {
725             notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10)
726         }
727         val notificationEntry = notificationEntryBuilder.build()
728         val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH)
729         if (overrideChannelVisibility) {
730             // User doesn't allow private notifications at the channel level
731             channel.lockscreenVisibility = VISIBILITY_PRIVATE
732         }
733         notificationEntry.setRanking(
734             RankingBuilder(notificationEntry.ranking)
735                 .setChannel(channel)
736                 .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
737                 .build()
738         )
739         return notificationEntry
740     }
741 
setupFgsNotificationEntrynull742     private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
743         return setupNotificationEntry(packageName, isFgs = true)
744     }
745 
setupCoreAppNotificationEntrynull746     private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry {
747         return setupNotificationEntry(packageName, isCoreApp = true)
748     }
749 
setupPublicNotificationEntrynull750     private fun setupPublicNotificationEntry(packageName: String): NotificationEntry {
751         return setupNotificationEntry(packageName, overrideVisibility = true)
752     }
753 
setupPublicNotificationEntryWithUserOverriddenChannelnull754     private fun setupPublicNotificationEntryWithUserOverriddenChannel(
755         packageName: String
756     ): NotificationEntry {
757         return setupNotificationEntry(
758             packageName,
759             overrideVisibility = true,
760             overrideChannelVisibility = true
761         )
762     }
763 
764     companion object {
765         private const val TEST_PROJECTION_PACKAGE_UID = 23
766         private const val BUGREPORT_PACKAGE_UID = 24
767         private const val TEST_PROJECTION_PACKAGE_NAME =
768             "com.android.systemui.statusbar.policy.projectionpackage"
769         private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
770         private const val EMERGENCY_ASSISTANCE_PACKAGE_NAME = "com.android.test.emergencyassistance"
771         private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler"
772     }
773 }
774