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 }