1 /*
2  * Copyright (C) 2022 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 package com.android.systemui.smartspace.filters
17 
18 import android.app.smartspace.SmartspaceTarget
19 import android.content.ContentResolver
20 import android.content.Context
21 import android.database.ContentObserver
22 import android.net.Uri
23 import android.os.Handler
24 import android.os.UserHandle
25 import android.provider.Settings
26 import com.android.systemui.dagger.qualifiers.Main
27 import com.android.systemui.settings.UserTracker
28 import com.android.systemui.smartspace.SmartspaceTargetFilter
29 import com.android.systemui.util.concurrency.Execution
30 import com.android.systemui.util.settings.SecureSettings
31 import java.util.concurrent.Executor
32 import javax.inject.Inject
33 
34 /**
35  * {@link SmartspaceTargetFilter} for smartspace targets that show above the lockscreen and dreams.
36  */
37 class LockscreenAndDreamTargetFilter @Inject constructor(
38     private val secureSettings: SecureSettings,
39     private val userTracker: UserTracker,
40     private val execution: Execution,
41     @Main private val handler: Handler,
42     private val contentResolver: ContentResolver,
43     @Main private val uiExecutor: Executor
44 ) : SmartspaceTargetFilter {
45     private var listeners: MutableSet<SmartspaceTargetFilter.Listener> = mutableSetOf()
46     private var showSensitiveContentForCurrentUser = false
47         set(value) {
48             val existing = field
49             field = value
50             if (existing != field) {
51                 listeners.forEach { it.onCriteriaChanged() }
52             }
53         }
54     private var showSensitiveContentForManagedUser = false
55         set(value) {
56             val existing = field
57             field = value
58             if (existing != field) {
59                 listeners.forEach { it.onCriteriaChanged() }
60             }
61         }
62 
63     private val settingsObserver = object : ContentObserver(handler) {
64         override fun onChange(selfChange: Boolean, uri: Uri?) {
65             execution.assertIsMainThread()
66             updateUserContentSettings()
67         }
68     }
69 
70     private var managedUserHandle: UserHandle? = null
71 
72     override fun addListener(listener: SmartspaceTargetFilter.Listener) {
73         listeners.add(listener)
74 
75         if (listeners.size != 1) {
76             return
77         }
78 
79         userTracker.addCallback(userTrackerCallback, uiExecutor)
80 
81         contentResolver.registerContentObserver(
82                 secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
83                 true,
84                 settingsObserver,
85                 UserHandle.USER_ALL
86         )
87 
88         updateUserContentSettings()
89     }
90 
91     override fun removeListener(listener: SmartspaceTargetFilter.Listener) {
92         listeners.remove(listener)
93 
94         if (listeners.isNotEmpty()) {
95             return
96         }
97 
98         userTracker.removeCallback(userTrackerCallback)
99         contentResolver.unregisterContentObserver(settingsObserver)
100     }
101 
102     override fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
103         return when (t.userHandle) {
104             userTracker.userHandle -> {
105                 !t.isSensitive || showSensitiveContentForCurrentUser
106             }
107             managedUserHandle -> {
108                 // Really, this should be "if this managed profile is associated with the current
109                 // active user", but we don't have a good way to check that, so instead we cheat:
110                 // Only the primary user can have an associated managed profile, so only show
111                 // content for the managed profile if the primary user is active
112                 userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
113                         (!t.isSensitive || showSensitiveContentForManagedUser)
114             }
115             else -> {
116                 false
117             }
118         }
119     }
120 
121     private val userTrackerCallback = object : UserTracker.Callback {
122         override fun onUserChanged(newUser: Int, userContext: Context) {
123             execution.assertIsMainThread()
124             updateUserContentSettings()
125         }
126     }
127 
128     private fun getWorkProfileUser(): UserHandle? {
129         for (userInfo in userTracker.userProfiles) {
130             if (userInfo.isManagedProfile) {
131                 return userInfo.userHandle
132             }
133         }
134         return null
135     }
136 
137     private fun updateUserContentSettings() {
138         val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
139 
140         showSensitiveContentForCurrentUser =
141                 secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1
142 
143         managedUserHandle = getWorkProfileUser()
144         val managedId = managedUserHandle?.identifier
145         if (managedId != null) {
146             showSensitiveContentForManagedUser =
147                     secureSettings.getIntForUser(setting, 0, managedId) == 1
148         }
149 
150         listeners.forEach { it.onCriteriaChanged() }
151     }
152 }
153