blob: d592492820c19421c4e8046f139b0b73c617cf58 [file] [log] [blame]
Fabian Kozynski8d06c712018-11-07 10:33:02 -05001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.privacy
18
19import android.app.ActivityManager
20import android.app.AppOpsManager
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050021import android.content.BroadcastReceiver
Fabian Kozynski8d06c712018-11-07 10:33:02 -050022import android.content.Context
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050023import android.content.Intent
24import android.content.IntentFilter
Lucas Dupin64171fe2019-10-30 14:28:29 -070025import android.os.Handler
26import android.os.Looper
27import android.os.Message
28import android.os.UserHandle
29import android.os.UserManager
Fabian Kozynski16b26992019-05-06 10:18:41 -040030import android.provider.DeviceConfig
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050031import com.android.internal.annotations.VisibleForTesting
Fabian Kozynski16b26992019-05-06 10:18:41 -040032import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
Dave Mankofff4736812019-10-18 17:25:50 -040033import com.android.systemui.Dumpable
Fabian Kozynski81765392019-02-11 12:38:26 -050034import com.android.systemui.R
Fabian Kozynski8d06c712018-11-07 10:33:02 -050035import com.android.systemui.appops.AppOpItem
36import com.android.systemui.appops.AppOpsController
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000037import com.android.systemui.broadcast.BroadcastDispatcher
Dave Mankofff4736812019-10-18 17:25:50 -040038import com.android.systemui.dagger.qualifiers.BgHandler
39import com.android.systemui.dagger.qualifiers.MainHandler
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050040import java.io.FileDescriptor
41import java.io.PrintWriter
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050042import java.lang.ref.WeakReference
43import javax.inject.Inject
44import javax.inject.Singleton
Fabian Kozynski8d06c712018-11-07 10:33:02 -050045
Fabian Kozynski16b26992019-05-06 10:18:41 -040046fun isPermissionsHubEnabled() = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
47 SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
48
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050049@Singleton
Fabian Kozynski81765392019-02-11 12:38:26 -050050class PrivacyItemController @Inject constructor(
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000051 private val context: Context,
Matt Papecb888792019-05-22 15:32:32 -070052 private val appOpsController: AppOpsController,
Dave Mankofff4736812019-10-18 17:25:50 -040053 @MainHandler private val uiHandler: Handler,
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000054 @BgHandler private val bgHandler: Handler,
55 private val broadcastDispatcher: BroadcastDispatcher
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050056) : Dumpable {
Fabian Kozynski8d06c712018-11-07 10:33:02 -050057
Fabian Kozynski16b26992019-05-06 10:18:41 -040058 @VisibleForTesting
59 internal companion object {
Fabian Kozynski8d06c712018-11-07 10:33:02 -050060 val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
61 AppOpsManager.OP_RECORD_AUDIO,
62 AppOpsManager.OP_COARSE_LOCATION,
63 AppOpsManager.OP_FINE_LOCATION)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050064 val intents = listOf(Intent.ACTION_USER_FOREGROUND,
65 Intent.ACTION_MANAGED_PROFILE_ADDED,
66 Intent.ACTION_MANAGED_PROFILE_REMOVED)
67 const val TAG = "PrivacyItemController"
Fabian Kozynski508422b2018-12-20 10:58:17 -050068 const val SYSTEM_UID = 1000
Fabian Kozynski16b26992019-05-06 10:18:41 -040069 const val MSG_ADD_CALLBACK = 0
70 const val MSG_REMOVE_CALLBACK = 1
71 const val MSG_UPDATE_LISTENING_STATE = 2
Fabian Kozynski8d06c712018-11-07 10:33:02 -050072 }
Fabian Kozynski508422b2018-12-20 10:58:17 -050073
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050074 @VisibleForTesting
75 internal var privacyList = emptyList<PrivacyItem>()
Fabian Kozynski72090102019-03-04 10:55:55 -050076 @Synchronized get() = field.toList() // Returns a shallow copy of the list
77 @Synchronized set
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050078
Fabian Kozynski8d06c712018-11-07 10:33:02 -050079 private val userManager = context.getSystemService(UserManager::class.java)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050080 private var currentUserIds = emptyList<Int>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050081 private var listening = false
Fabian Kozynski0d03da32019-01-28 10:35:28 -050082 val systemApp =
83 PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050084 private val callbacks = mutableListOf<WeakReference<Callback>>()
Fabian Kozynski16b26992019-05-06 10:18:41 -040085 private val messageHandler = H(WeakReference(this), uiHandler.looper)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050086
Fabian Kozynski8d06c712018-11-07 10:33:02 -050087 private val notifyChanges = Runnable {
Fabian Kozynski72090102019-03-04 10:55:55 -050088 val list = privacyList
89 callbacks.forEach { it.get()?.privacyChanged(list) }
Fabian Kozynski8d06c712018-11-07 10:33:02 -050090 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050091
Fabian Kozynski8d06c712018-11-07 10:33:02 -050092 private val updateListAndNotifyChanges = Runnable {
93 updatePrivacyList()
94 uiHandler.post(notifyChanges)
95 }
96
Fabian Kozynski16b26992019-05-06 10:18:41 -040097 private var indicatorsAvailable = isPermissionsHubEnabled()
98 @VisibleForTesting
Matt Papecb888792019-05-22 15:32:32 -070099 internal val devicePropertiesChangedListener =
100 object : DeviceConfig.OnPropertiesChangedListener {
101 override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
102 if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
103 properties.getKeyset().contains(
104 SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED)) {
105 indicatorsAvailable = properties.getBoolean(
106 SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
Fabian Kozynski16b26992019-05-06 10:18:41 -0400107 messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
108 messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
109 }
110 }
111 }
112
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500113 private val cb = object : AppOpsController.Callback {
114 override fun onActiveStateChanged(
115 code: Int,
116 uid: Int,
117 packageName: String,
118 active: Boolean
119 ) {
120 val userId = UserHandle.getUserId(uid)
121 if (userId in currentUserIds) {
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500122 update(false)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500123 }
124 }
125 }
126
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500127 @VisibleForTesting
128 internal var userSwitcherReceiver = Receiver()
129 set(value) {
130 context.unregisterReceiver(field)
131 field = value
132 registerReceiver()
133 }
134
Fabian Kozynski16b26992019-05-06 10:18:41 -0400135 init {
Matt Papecb888792019-05-22 15:32:32 -0700136 DeviceConfig.addOnPropertiesChangedListener(
137 DeviceConfig.NAMESPACE_PRIVACY,
138 context.mainExecutor,
139 devicePropertiesChangedListener)
Fabian Kozynski16b26992019-05-06 10:18:41 -0400140 }
141
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500142 private fun unregisterReceiver() {
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000143 broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500144 }
145
146 private fun registerReceiver() {
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000147 broadcastDispatcher.registerReceiver(userSwitcherReceiver, IntentFilter().apply {
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500148 intents.forEach {
149 addAction(it)
150 }
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000151 }, null /* handler */, UserHandle.ALL)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500152 }
153
154 private fun update(updateUsers: Boolean) {
155 if (updateUsers) {
156 val currentUser = ActivityManager.getCurrentUser()
157 currentUserIds = userManager.getProfiles(currentUser).map { it.id }
158 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500159 bgHandler.post(updateListAndNotifyChanges)
160 }
161
Fabian Kozynski16b26992019-05-06 10:18:41 -0400162 /**
163 * Updates listening status based on whether there are callbacks and the indicators are enabled
164 *
165 * This is only called from private (add/remove)Callback and from the config listener, all in
166 * main thread.
167 */
168 private fun setListeningState() {
169 val listen = !callbacks.isEmpty() and indicatorsAvailable
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500170 if (listening == listen) return
171 listening = listen
172 if (listening) {
173 appOpsController.addCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500174 registerReceiver()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500175 update(true)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500176 } else {
177 appOpsController.removeCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500178 unregisterReceiver()
Fabian Kozynski16b26992019-05-06 10:18:41 -0400179 // Make sure that we remove all indicators and notify listeners if we are not
180 // listening anymore due to indicators being disabled
181 update(false)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500182 }
183 }
184
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500185 private fun addCallback(callback: WeakReference<Callback>) {
186 callbacks.add(callback)
Fabian Kozynski16b26992019-05-06 10:18:41 -0400187 if (callbacks.isNotEmpty() && !listening) {
188 messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
189 messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
190 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500191 // Notify this callback if we didn't set to listening
Fabian Kozynski16b26992019-05-06 10:18:41 -0400192 else if (listening) uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500193 }
194
195 private fun removeCallback(callback: WeakReference<Callback>) {
196 // Removes also if the callback is null
197 callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
Fabian Kozynski16b26992019-05-06 10:18:41 -0400198 if (callbacks.isEmpty()) {
199 messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
200 messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
201 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500202 }
203
204 fun addCallback(callback: Callback) {
Fabian Kozynski16b26992019-05-06 10:18:41 -0400205 messageHandler.obtainMessage(MSG_ADD_CALLBACK, callback).sendToTarget()
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500206 }
207
208 fun removeCallback(callback: Callback) {
Fabian Kozynski16b26992019-05-06 10:18:41 -0400209 messageHandler.obtainMessage(MSG_REMOVE_CALLBACK, callback).sendToTarget()
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500210 }
211
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500212 private fun updatePrivacyList() {
Fabian Kozynski16b26992019-05-06 10:18:41 -0400213 if (!listening) {
214 privacyList = emptyList()
215 return
216 }
Fabian Kozynski72090102019-03-04 10:55:55 -0500217 val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500218 .mapNotNull { toPrivacyItem(it) }.distinct()
Fabian Kozynski72090102019-03-04 10:55:55 -0500219 privacyList = list
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500220 }
221
222 private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
223 val type: PrivacyType = when (appOpItem.code) {
224 AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
225 AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
226 AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
227 AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
228 else -> return null
229 }
Fabian Kozynski508422b2018-12-20 10:58:17 -0500230 if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
Fabian Kozynski0d03da32019-01-28 10:35:28 -0500231 val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
Fabian Kozynskief124492018-11-02 11:02:11 -0400232 return PrivacyItem(type, app)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500233 }
234
235 // Used by containing class to get notified of changes
236 interface Callback {
237 fun privacyChanged(privacyItems: List<PrivacyItem>)
238 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500239
240 internal inner class Receiver : BroadcastReceiver() {
241 override fun onReceive(context: Context?, intent: Intent?) {
242 if (intent?.action in intents) {
243 update(true)
244 }
245 }
246 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500247
248 private class NotifyChangesToCallback(
249 private val callback: Callback?,
250 private val list: List<PrivacyItem>
251 ) : Runnable {
252 override fun run() {
253 callback?.privacyChanged(list)
254 }
255 }
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500256
Lucas Dupin64171fe2019-10-30 14:28:29 -0700257 override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
258 pw.println("PrivacyItemController state:")
259 pw.println(" Listening: $listening")
260 pw.println(" Current user ids: $currentUserIds")
261 pw.println(" Privacy Items:")
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500262 privacyList.forEach {
Lucas Dupin64171fe2019-10-30 14:28:29 -0700263 pw.print(" ")
264 pw.println(it.toString())
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500265 }
Lucas Dupin64171fe2019-10-30 14:28:29 -0700266 pw.println(" Callbacks:")
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500267 callbacks.forEach {
268 it.get()?.let {
Lucas Dupin64171fe2019-10-30 14:28:29 -0700269 pw.print(" ")
270 pw.println(it.toString())
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500271 }
272 }
273 }
Fabian Kozynski16b26992019-05-06 10:18:41 -0400274
275 private class H(
Matt Papecb888792019-05-22 15:32:32 -0700276 private val outerClass: WeakReference<PrivacyItemController>,
277 looper: Looper
Fabian Kozynski16b26992019-05-06 10:18:41 -0400278 ) : Handler(looper) {
279 override fun handleMessage(msg: Message) {
280 super.handleMessage(msg)
281 when (msg.what) {
282 MSG_UPDATE_LISTENING_STATE -> outerClass.get()?.setListeningState()
283
284 MSG_ADD_CALLBACK -> {
285 if (msg.obj !is PrivacyItemController.Callback) return
286 outerClass.get()?.addCallback(
287 WeakReference(msg.obj as PrivacyItemController.Callback))
288 }
289
290 MSG_REMOVE_CALLBACK -> {
291 if (msg.obj !is PrivacyItemController.Callback) return
292 outerClass.get()?.removeCallback(
293 WeakReference(msg.obj as PrivacyItemController.Callback))
294 }
295 else -> {}
296 }
297 }
298 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500299}