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.statusbar.notification.collection.coordinator
18 
19 import android.os.UserHandle
20 import android.service.notification.StatusBarNotification
21 import androidx.test.filters.SmallTest
22 import com.android.keyguard.KeyguardUpdateMonitor
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.plugins.statusbar.StatusBarStateController
25 import com.android.systemui.statusbar.NotificationLockscreenUserManager
26 import com.android.systemui.statusbar.StatusBarState
27 import com.android.systemui.statusbar.notification.DynamicPrivacyController
28 import com.android.systemui.statusbar.notification.collection.ListEntry
29 import com.android.systemui.statusbar.notification.collection.NotifPipeline
30 import com.android.systemui.statusbar.notification.collection.NotificationEntry
31 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
32 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
33 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
34 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
35 import com.android.systemui.statusbar.policy.KeyguardStateController
36 import com.android.systemui.util.mockito.any
37 import com.android.systemui.util.mockito.eq
38 import com.android.systemui.util.mockito.mock
39 import com.android.systemui.util.mockito.withArgCaptor
40 import dagger.BindsInstance
41 import dagger.Component
42 import org.junit.Test
43 import org.mockito.Mockito.never
44 import org.mockito.Mockito.verify
45 import org.mockito.Mockito.`when` as whenever
46 
47 @SmallTest
48 class SensitiveContentCoordinatorTest : SysuiTestCase() {
49 
50     val dynamicPrivacyController: DynamicPrivacyController = mock()
51     val lockscreenUserManager: NotificationLockscreenUserManager = mock()
52     val pipeline: NotifPipeline = mock()
53     val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
54     val statusBarStateController: StatusBarStateController = mock()
55     val keyguardStateController: KeyguardStateController = mock()
56 
57     val coordinator: SensitiveContentCoordinator =
58         DaggerTestSensitiveContentCoordinatorComponent
59                 .factory()
60                 .create(
61                         dynamicPrivacyController,
62                         lockscreenUserManager,
63                         keyguardUpdateMonitor,
64                         statusBarStateController,
65                         keyguardStateController)
66                 .coordinator
67 
68     @Test
69     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
70         coordinator.attach(pipeline)
71         val invalidator = withArgCaptor<Invalidator> {
72             verify(pipeline).addPreRenderInvalidator(capture())
73         }
74         val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
75             verify(dynamicPrivacyController).addListener(capture())
76         }
77 
78         val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
79         invalidator.setInvalidationListener(invalidationListener)
80 
81         dynamicPrivacyListener.onDynamicPrivacyChanged()
82 
83         verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any())
84     }
85 
86     @Test
87     fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
88         coordinator.attach(pipeline)
89         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
90             verify(pipeline).addOnBeforeRenderListListener(capture())
91         }
92 
93         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
94         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
95         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
96         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
97         val entry = fakeNotification(1, false)
98 
99         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
100 
101         verify(entry.representativeEntry!!).setSensitive(false, false)
102     }
103 
104     @Test
105     fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
106         coordinator.attach(pipeline)
107         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
108             verify(pipeline).addOnBeforeRenderListListener(capture())
109         }
110 
111         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
112         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
113         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
114         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
115         val entry = fakeNotification(1, true)
116 
117         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
118 
119         verify(entry.representativeEntry!!).setSensitive(false, false)
120     }
121 
122     @Test
123     fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
124         coordinator.attach(pipeline)
125         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
126             verify(pipeline).addOnBeforeRenderListListener(capture())
127         }
128 
129         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
130         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
131         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
132         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
133         val entry = fakeNotification(1, false)
134 
135         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
136 
137         verify(entry.representativeEntry!!).setSensitive(false, false)
138     }
139 
140     @Test
141     fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
142         coordinator.attach(pipeline)
143         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
144             verify(pipeline).addOnBeforeRenderListListener(capture())
145         }
146 
147         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
148         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
149         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
150         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
151         val entry = fakeNotification(1, false)
152 
153         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
154 
155         verify(entry.representativeEntry!!).setSensitive(false, true)
156     }
157 
158     @Test
159     fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
160         coordinator.attach(pipeline)
161         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
162             verify(pipeline).addOnBeforeRenderListListener(capture())
163         }
164 
165         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
166         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
167         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
168         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
169         val entry = fakeNotification(1, true)
170 
171         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
172 
173         verify(entry.representativeEntry!!).setSensitive(true, true)
174     }
175 
176     @Test
177     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
178         coordinator.attach(pipeline)
179         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
180             verify(pipeline).addOnBeforeRenderListListener(capture())
181         }
182 
183         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
184         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
185         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
186         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
187         val entry = fakeNotification(1, true)
188 
189         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
190 
191         verify(entry.representativeEntry!!).setSensitive(false, true)
192     }
193 
194     @Test
195     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
196         coordinator.attach(pipeline)
197         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
198             verify(pipeline).addOnBeforeRenderListListener(capture())
199         }
200 
201         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
202         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
203         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
204         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
205         whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
206 
207         val entry = fakeNotification(2, true)
208 
209         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
210 
211         verify(entry.representativeEntry!!).setSensitive(true, true)
212     }
213 
214     @Test
215     fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
216         coordinator.attach(pipeline)
217         val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
218             verify(pipeline).addOnBeforeRenderListListener(capture())
219         }
220 
221         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
222         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
223         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
224         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
225         whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
226         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
227                 .thenReturn(true)
228 
229         val entry = fakeNotification(2, true)
230 
231         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
232 
233         verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
234     }
235 
236     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
237         val mockUserHandle = mock<UserHandle>().apply {
238             whenever(identifier).thenReturn(notifUserId)
239         }
240         val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
241             whenever(user).thenReturn(mockUserHandle)
242         }
243         val mockEntry = mock<NotificationEntry>().apply {
244             whenever(sbn).thenReturn(mockSbn)
245         }
246         whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
247         whenever(mockEntry.rowExists()).thenReturn(true)
248         return object : ListEntry("key", 0) {
249             override fun getRepresentativeEntry(): NotificationEntry = mockEntry
250         }
251     }
252 }
253 
254 @CoordinatorScope
255 @Component(modules = [SensitiveContentCoordinatorModule::class])
256 interface TestSensitiveContentCoordinatorComponent {
257     val coordinator: SensitiveContentCoordinator
258 
259     @Component.Factory
260     interface Factory {
261         fun create(
262             @BindsInstance dynamicPrivacyController: DynamicPrivacyController,
263             @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
264             @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
265             @BindsInstance statusBarStateController: StatusBarStateController,
266             @BindsInstance keyguardStateController: KeyguardStateController
267         ): TestSensitiveContentCoordinatorComponent
268     }
269 }
270