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.keyguard.KeyguardUpdateMonitor 21 import com.android.systemui.plugins.statusbar.StatusBarStateController 22 import com.android.systemui.statusbar.NotificationLockscreenUserManager 23 import com.android.systemui.statusbar.StatusBarState 24 import com.android.systemui.statusbar.notification.DynamicPrivacyController 25 import com.android.systemui.statusbar.notification.collection.GroupEntry 26 import com.android.systemui.statusbar.notification.collection.ListEntry 27 import com.android.systemui.statusbar.notification.collection.NotifPipeline 28 import com.android.systemui.statusbar.notification.collection.NotificationEntry 29 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope 30 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener 31 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator 32 import com.android.systemui.statusbar.policy.KeyguardStateController 33 import dagger.Binds 34 import dagger.Module 35 import javax.inject.Inject 36 37 @Module(includes = [PrivateSensitiveContentCoordinatorModule::class]) 38 interface SensitiveContentCoordinatorModule 39 40 @Module 41 private interface PrivateSensitiveContentCoordinatorModule { 42 @Binds 43 fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator 44 } 45 46 /** Coordinates re-inflation and post-processing of sensitive notification content. */ 47 interface SensitiveContentCoordinator : Coordinator 48 49 @CoordinatorScope 50 private class SensitiveContentCoordinatorImpl @Inject constructor( 51 private val dynamicPrivacyController: DynamicPrivacyController, 52 private val lockscreenUserManager: NotificationLockscreenUserManager, 53 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 54 private val statusBarStateController: StatusBarStateController, 55 private val keyguardStateController: KeyguardStateController 56 ) : Invalidator("SensitiveContentInvalidator"), 57 SensitiveContentCoordinator, 58 DynamicPrivacyController.Listener, 59 OnBeforeRenderListListener { 60 61 override fun attach(pipeline: NotifPipeline) { 62 dynamicPrivacyController.addListener(this) 63 pipeline.addOnBeforeRenderListListener(this) 64 pipeline.addPreRenderInvalidator(this) 65 } 66 67 override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged") 68 69 override fun onBeforeRenderList(entries: List<ListEntry>) { 70 if (keyguardStateController.isKeyguardGoingAway() || 71 statusBarStateController.getState() == StatusBarState.KEYGUARD && 72 keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing( 73 KeyguardUpdateMonitor.getCurrentUser())) { 74 // don't update yet if: 75 // - the keyguard is currently going away 76 // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash) 77 78 // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the 79 // dependent state changes invalidate the pipeline 80 return 81 } 82 83 val currentUserId = lockscreenUserManager.currentUserId 84 val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) 85 val deviceSensitive = devicePublic && 86 !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) 87 val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked 88 for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { 89 val notifUserId = entry.sbn.user.identifier 90 val userLockscreen = devicePublic || 91 lockscreenUserManager.isLockscreenPublicMode(notifUserId) 92 val userPublic = when { 93 // if we're not on the lockscreen, we're definitely private 94 !userLockscreen -> false 95 // we are on the lockscreen, so unless we're dynamically unlocked, we're 96 // definitely public 97 !dynamicallyUnlocked -> true 98 // we're dynamically unlocked, but check if the notification needs 99 // a separate challenge if it's from a work profile 100 else -> when (notifUserId) { 101 currentUserId -> false 102 UserHandle.USER_ALL -> false 103 else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) 104 } 105 } 106 val needsRedaction = lockscreenUserManager.needsRedaction(entry) 107 val isSensitive = userPublic && needsRedaction 108 entry.setSensitive(isSensitive, deviceSensitive) 109 } 110 } 111 } 112 113 private fun extractAllRepresentativeEntries( 114 entries: List<ListEntry> 115 ): Sequence<NotificationEntry> = 116 entries.asSequence().flatMap(::extractAllRepresentativeEntries) 117 118 private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> = 119 sequence { 120 listEntry.representativeEntry?.let { yield(it) } 121 if (listEntry is GroupEntry) { 122 yieldAll(extractAllRepresentativeEntries(listEntry.children)) 123 } 124 } 125