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 com.android.systemui.statusbar.NotificationLockscreenUserManager
21 import com.android.systemui.statusbar.notification.DynamicPrivacyController
22 import com.android.systemui.statusbar.notification.collection.GroupEntry
23 import com.android.systemui.statusbar.notification.collection.ListEntry
24 import com.android.systemui.statusbar.notification.collection.NotifPipeline
25 import com.android.systemui.statusbar.notification.collection.NotificationEntry
26 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
27 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
28 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
29 import dagger.Module
30 import dagger.Provides
31 
32 @Module
33 object SensitiveContentCoordinatorModule {
34     @Provides
35     @JvmStatic
36     @CoordinatorScope
37     fun provideCoordinator(
38         dynamicPrivacyController: DynamicPrivacyController,
39         lockscreenUserManager: NotificationLockscreenUserManager
40     ): SensitiveContentCoordinator =
41             SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
42 }
43 
44 /** Coordinates re-inflation and post-processing of sensitive notification content. */
45 interface SensitiveContentCoordinator : Coordinator
46 
47 private class SensitiveContentCoordinatorImpl(
48     private val dynamicPrivacyController: DynamicPrivacyController,
49     private val lockscreenUserManager: NotificationLockscreenUserManager
50 ) : Invalidator("SensitiveContentInvalidator"),
51         SensitiveContentCoordinator,
52         DynamicPrivacyController.Listener,
53         OnBeforeRenderListListener {
54 
55     override fun attach(pipeline: NotifPipeline) {
56         dynamicPrivacyController.addListener(this)
57         pipeline.addOnBeforeRenderListListener(this)
58         pipeline.addPreRenderInvalidator(this)
59     }
60 
61     override fun onDynamicPrivacyChanged(): Unit = invalidateList()
62 
63     override fun onBeforeRenderList(entries: List<ListEntry>) {
64         val currentUserId = lockscreenUserManager.currentUserId
65         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
66         val deviceSensitive = devicePublic &&
67                 !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
68         val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
69         for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
70             val notifUserId = entry.sbn.user.identifier
71             val userLockscreen = devicePublic ||
72                     lockscreenUserManager.isLockscreenPublicMode(notifUserId)
73             val userPublic = when {
74                 // if we're not on the lockscreen, we're definitely private
75                 !userLockscreen -> false
76                 // we are on the lockscreen, so unless we're dynamically unlocked, we're
77                 // definitely public
78                 !dynamicallyUnlocked -> true
79                 // we're dynamically unlocked, but check if the notification needs
80                 // a separate challenge if it's from a work profile
81                 else -> when (notifUserId) {
82                     currentUserId -> false
83                     UserHandle.USER_ALL -> false
84                     else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
85                 }
86             }
87             val needsRedaction = lockscreenUserManager.needsRedaction(entry)
88             val isSensitive = userPublic && needsRedaction
89             entry.setSensitive(isSensitive, deviceSensitive)
90         }
91     }
92 }
93 
94 private fun extractAllRepresentativeEntries(
95     entries: List<ListEntry>
96 ): Sequence<NotificationEntry> =
97     entries.asSequence().flatMap(::extractAllRepresentativeEntries)
98 
99 private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
100     sequence {
101         listEntry.representativeEntry?.let { yield(it) }
102         if (listEntry is GroupEntry) {
103             yieldAll(extractAllRepresentativeEntries(listEntry.children))
104         }
105     }