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