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.keyguard.KeyguardUpdateMonitor
22 import com.android.server.notification.Flags.screenshareNotificationHiding
23 import com.android.systemui.plugins.statusbar.StatusBarStateController
24 import com.android.systemui.statusbar.NotificationLockscreenUserManager
25 import com.android.systemui.statusbar.StatusBarState
26 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
27 import com.android.systemui.statusbar.notification.DynamicPrivacyController
28 import com.android.systemui.statusbar.notification.collection.GroupEntry
29 import com.android.systemui.statusbar.notification.collection.ListEntry
30 import com.android.systemui.statusbar.notification.collection.NotifPipeline
31 import com.android.systemui.statusbar.notification.collection.NotificationEntry
32 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
33 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
34 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
35 import com.android.systemui.statusbar.policy.KeyguardStateController
36 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
37 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
38 import dagger.Binds
39 import dagger.Module
40 import javax.inject.Inject
41 
42 @Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
43 interface SensitiveContentCoordinatorModule
44 
45 @Module
46 interface PrivateSensitiveContentCoordinatorModule {
47     @Binds
bindCoordinatornull48     fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
49 }
50 
51 /** Coordinates re-inflation and post-processing of sensitive notification content. */
52 interface SensitiveContentCoordinator : Coordinator
53 
54 @CoordinatorScope
55 class SensitiveContentCoordinatorImpl @Inject constructor(
56     private val dynamicPrivacyController: DynamicPrivacyController,
57     private val lockscreenUserManager: NotificationLockscreenUserManager,
58     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
59     private val statusBarStateController: StatusBarStateController,
60     private val keyguardStateController: KeyguardStateController,
61     private val selectedUserInteractor: SelectedUserInteractor,
62     private val sensitiveNotificationProtectionController:
63         SensitiveNotificationProtectionController,
64 ) : Invalidator("SensitiveContentInvalidator"),
65         SensitiveContentCoordinator,
66         DynamicPrivacyController.Listener,
67         OnBeforeRenderListListener {
68     private val onSensitiveStateChanged = Runnable() {
69         invalidateList("onSensitiveStateChanged")
70     }
71 
72     private val screenshareSecretFilter = object : NotifFilter("ScreenshareSecretFilter") {
73         val NotificationEntry.isSecret
74             get() = channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
75                 sbn.notification?.visibility == Notification.VISIBILITY_SECRET
76         override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
77             return screenshareNotificationHiding() &&
78                 sensitiveNotificationProtectionController.isSensitiveStateActive &&
79                 entry.isSecret
80         }
81     }
82 
83     override fun attach(pipeline: NotifPipeline) {
84         dynamicPrivacyController.addListener(this)
85         if (screenshareNotificationHiding()) {
86             sensitiveNotificationProtectionController
87                 .registerSensitiveStateListener(onSensitiveStateChanged)
88         }
89         pipeline.addOnBeforeRenderListListener(this)
90         pipeline.addPreRenderInvalidator(this)
91         if (screenshareNotificationHiding()) {
92             pipeline.addFinalizeFilter(screenshareSecretFilter)
93         }
94     }
95 
96     override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
97 
98     override fun onBeforeRenderList(entries: List<ListEntry>) {
99         if (keyguardStateController.isKeyguardGoingAway ||
100                 statusBarStateController.state == StatusBarState.KEYGUARD &&
101                 keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
102                         selectedUserInteractor.getSelectedUserId())) {
103             // don't update yet if:
104             // - the keyguard is currently going away
105             // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
106 
107             // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
108             // dependent state changes invalidate the pipeline
109             return
110         }
111 
112         val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
113             sensitiveNotificationProtectionController.isSensitiveStateActive
114         val currentUserId = lockscreenUserManager.currentUserId
115         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
116         val deviceSensitive = (devicePublic &&
117                 !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
118                 isSensitiveContentProtectionActive
119         val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
120         for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
121             val notifUserId = entry.sbn.user.identifier
122             val userLockscreen = devicePublic ||
123                     lockscreenUserManager.isLockscreenPublicMode(notifUserId)
124             val userPublic = when {
125                 // if we're not on the lockscreen, we're definitely private
126                 !userLockscreen -> false
127                 // we are on the lockscreen, so unless we're dynamically unlocked, we're
128                 // definitely public
129                 !dynamicallyUnlocked -> true
130                 // we're dynamically unlocked, but check if the notification needs
131                 // a separate challenge if it's from a work profile
132                 else -> when (notifUserId) {
133                     currentUserId -> false
134                     UserHandle.USER_ALL -> false
135                     else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
136                 }
137             }
138 
139             val shouldProtectNotification = screenshareNotificationHiding() &&
140                 sensitiveNotificationProtectionController.shouldProtectNotification(entry)
141 
142             val needsRedaction = lockscreenUserManager.needsRedaction(entry)
143             val isSensitive = userPublic && needsRedaction
144             entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
145             if (screenshareNotificationHiding()) {
146                 entry.row?.setPublicExpanderVisible(!shouldProtectNotification)
147             }
148         }
149     }
150 }
151 
extractAllRepresentativeEntriesnull152 private fun extractAllRepresentativeEntries(
153     entries: List<ListEntry>
154 ): Sequence<NotificationEntry> =
155     entries.asSequence().flatMap(::extractAllRepresentativeEntries)
156 
157 private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
158     sequence {
159         listEntry.representativeEntry?.let { yield(it) }
160         if (listEntry is GroupEntry) {
161             yieldAll(extractAllRepresentativeEntries(listEntry.children))
162         }
163     }
164