• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.Notification
20 import android.os.UserHandle
21 import com.android.app.tracing.coroutines.launchTraced as launch
22 import com.android.keyguard.KeyguardUpdateMonitor
23 import com.android.server.notification.Flags.screenshareNotificationHiding
24 import com.android.systemui.dagger.qualifiers.Application
25 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
26 import com.android.systemui.plugins.statusbar.StatusBarStateController
27 import com.android.systemui.scene.domain.interactor.SceneInteractor
28 import com.android.systemui.scene.shared.flag.SceneContainerFlag
29 import com.android.systemui.scene.shared.model.Scenes
30 import com.android.systemui.statusbar.NotificationLockscreenUserManager
31 import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
32 import com.android.systemui.statusbar.StatusBarState
33 import com.android.systemui.statusbar.notification.DynamicPrivacyController
34 import com.android.systemui.statusbar.notification.collection.GroupEntry
35 import com.android.systemui.statusbar.notification.collection.PipelineEntry
36 import com.android.systemui.statusbar.notification.collection.NotifPipeline
37 import com.android.systemui.statusbar.notification.collection.NotificationEntry
38 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
39 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
40 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
41 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
42 import com.android.systemui.statusbar.policy.KeyguardStateController
43 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
44 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
45 import dagger.Binds
46 import dagger.Module
47 import javax.inject.Inject
48 import kotlinx.coroutines.CoroutineScope
49 import kotlinx.coroutines.flow.distinctUntilChanged
50 import kotlinx.coroutines.flow.mapNotNull
51 
52 @Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
53 interface SensitiveContentCoordinatorModule
54 
55 @Module
56 interface PrivateSensitiveContentCoordinatorModule {
bindCoordinatornull57     @Binds fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
58 }
59 
60 /** Coordinates re-inflation and post-processing of sensitive notification content. */
61 interface SensitiveContentCoordinator : Coordinator
62 
63 @CoordinatorScope
64 class SensitiveContentCoordinatorImpl
65 @Inject
66 constructor(
67     private val dynamicPrivacyController: DynamicPrivacyController,
68     private val lockscreenUserManager: NotificationLockscreenUserManager,
69     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
70     private val statusBarStateController: StatusBarStateController,
71     private val keyguardStateController: KeyguardStateController,
72     private val selectedUserInteractor: SelectedUserInteractor,
73     private val sensitiveNotificationProtectionController:
74         SensitiveNotificationProtectionController,
75     private val deviceEntryInteractor: DeviceEntryInteractor,
76     private val sceneInteractor: SceneInteractor,
77     @Application private val scope: CoroutineScope,
78 ) :
79     Invalidator("SensitiveContentInvalidator"),
80     SensitiveContentCoordinator,
81     DynamicPrivacyController.Listener,
82     OnBeforeRenderListListener {
83     private var inTransitionFromLockedToGone = false
84     private var canSwipeToEnter = false
85 
86     private val onSensitiveStateChanged = Runnable() { invalidateList("onSensitiveStateChanged") }
87 
88     private val screenshareSecretFilter =
89         object : NotifFilter("ScreenshareSecretFilter") {
90             val NotificationEntry.isSecret
91                 get() =
92                     channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
93                         sbn.notification?.visibility == Notification.VISIBILITY_SECRET
94 
95             override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
96                 return screenshareNotificationHiding() &&
97                     sensitiveNotificationProtectionController.isSensitiveStateActive &&
98                     entry.isSecret
99             }
100         }
101 
102     override fun attach(pipeline: NotifPipeline) {
103         if (!SceneContainerFlag.isEnabled) {
104             dynamicPrivacyController.addListener(this)
105         }
106         if (screenshareNotificationHiding()) {
107             sensitiveNotificationProtectionController.registerSensitiveStateListener(
108                 onSensitiveStateChanged
109             )
110         }
111         pipeline.addOnBeforeRenderListListener(this)
112         pipeline.addPreRenderInvalidator(this)
113         if (screenshareNotificationHiding()) {
114             pipeline.addFinalizeFilter(screenshareSecretFilter)
115         }
116 
117         if (SceneContainerFlag.isEnabled) {
118             scope.launch {
119                 sceneInteractor.transitionState
120                     .mapNotNull {
121                         val transitioningToGone = it.isTransitioning(to = Scenes.Gone)
122                         val deviceEntered = deviceEntryInteractor.isDeviceEntered.value
123                         when {
124                             transitioningToGone && !deviceEntered -> true
125                             !transitioningToGone -> false
126                             else -> null
127                         }
128                     }
129                     .distinctUntilChanged()
130                     .collect {
131                         inTransitionFromLockedToGone = it
132                         invalidateList("inTransitionFromLockedToGoneChanged")
133                     }
134             }
135             scope.launch {
136                 deviceEntryInteractor.canSwipeToEnter.collect {
137                     val canSwipeToEnter = it ?: false
138                     if (this@SensitiveContentCoordinatorImpl.canSwipeToEnter != canSwipeToEnter) {
139                         this@SensitiveContentCoordinatorImpl.canSwipeToEnter = canSwipeToEnter
140                         invalidateList("canSwipeToEnterChanged")
141                     }
142                 }
143             }
144         }
145     }
146 
147     override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
148 
149     private val isKeyguardGoingAway: Boolean
150         get() {
151             if (SceneContainerFlag.isEnabled) {
152                 return inTransitionFromLockedToGone
153             } else {
154                 return keyguardStateController.isKeyguardGoingAway
155             }
156         }
157 
158     override fun onBeforeRenderList(entries: List<PipelineEntry>) {
159         if (
160             isKeyguardGoingAway ||
161                 statusBarStateController.state == StatusBarState.KEYGUARD &&
162                     keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
163                         selectedUserInteractor.getSelectedUserId()
164                     )
165         ) {
166             // don't update yet if:
167             // - the keyguard is currently going away
168             // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
169 
170             // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
171             // dependent state changes invalidate the pipeline
172             return
173         }
174 
175         val isSensitiveContentProtectionActive =
176             screenshareNotificationHiding() &&
177                 sensitiveNotificationProtectionController.isSensitiveStateActive
178         val currentUserId = lockscreenUserManager.currentUserId
179         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
180         val deviceSensitive =
181             (devicePublic &&
182                 !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
183                 isSensitiveContentProtectionActive
184         val dynamicallyUnlocked =
185             if (SceneContainerFlag.isEnabled) canSwipeToEnter
186             else dynamicPrivacyController.isDynamicallyUnlocked
187         for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
188             val notifUserId = entry.sbn.user.identifier
189             val userLockscreen =
190                 devicePublic || lockscreenUserManager.isLockscreenPublicMode(notifUserId)
191             val userPublic =
192                 when {
193                     // if we're not on the lockscreen, we're definitely private
194                     !userLockscreen -> false
195                     // we are on the lockscreen, so unless we're dynamically unlocked, we're
196                     // definitely public
197                     !dynamicallyUnlocked -> true
198                     // we're dynamically unlocked, but check if the notification needs
199                     // a separate challenge if it's from a work profile
200                     else ->
201                         when (notifUserId) {
202                             currentUserId -> false
203                             UserHandle.USER_ALL -> false
204                             else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
205                         }
206                 }
207 
208             val shouldProtectNotification =
209                 screenshareNotificationHiding() &&
210                     sensitiveNotificationProtectionController.shouldProtectNotification(entry)
211 
212             val needsRedaction =
213                 lockscreenUserManager.getRedactionType(entry) != REDACTION_TYPE_NONE
214             val isSensitive = userPublic && needsRedaction
215             entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
216             if (screenshareNotificationHiding()) {
217                 entry.row?.setPublicExpanderVisible(!shouldProtectNotification)
218             }
219         }
220     }
221 }
222 
extractAllRepresentativeEntriesnull223 private fun extractAllRepresentativeEntries(entries: List<PipelineEntry>): Sequence<NotificationEntry> =
224     entries.asSequence().flatMap(::extractAllRepresentativeEntries)
225 
226 private fun extractAllRepresentativeEntries(
227     pipelineEntry: PipelineEntry,
228 ): Sequence<NotificationEntry> =
229     sequence {
230         pipelineEntry.representativeEntry?.let { yield(it) }
231         if (pipelineEntry is GroupEntry) {
232             yieldAll(extractAllRepresentativeEntries(pipelineEntry.children))
233         }
234     }
235