blob: 625eacd7e2a76547f000fe653383f4fb9eee8b9f [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
Fabian Kozynski8d06c712018-11-07 10:33:02 -050025import android.os.Handler
26import android.os.UserHandle
27import android.os.UserManager
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050028import com.android.internal.annotations.VisibleForTesting
Fabian Kozynski81765392019-02-11 12:38:26 -050029import com.android.systemui.Dependency.BG_HANDLER_NAME
30import com.android.systemui.Dependency.MAIN_HANDLER_NAME
31import com.android.systemui.R
Fabian Kozynski8d06c712018-11-07 10:33:02 -050032import com.android.systemui.appops.AppOpItem
33import com.android.systemui.appops.AppOpsController
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050034import com.android.systemui.Dumpable
35import java.io.FileDescriptor
36import java.io.PrintWriter
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050037import java.lang.ref.WeakReference
38import javax.inject.Inject
Fabian Kozynski81765392019-02-11 12:38:26 -050039import javax.inject.Named
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050040import javax.inject.Singleton
Fabian Kozynski8d06c712018-11-07 10:33:02 -050041
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050042@Singleton
Fabian Kozynski81765392019-02-11 12:38:26 -050043class PrivacyItemController @Inject constructor(
44 val context: Context,
45 private val appOpsController: AppOpsController,
46 @Named(MAIN_HANDLER_NAME) private val uiHandler: Handler,
47 @Named(BG_HANDLER_NAME) private val bgHandler: Handler
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050048) : Dumpable {
Fabian Kozynski8d06c712018-11-07 10:33:02 -050049
50 companion object {
51 val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
52 AppOpsManager.OP_RECORD_AUDIO,
53 AppOpsManager.OP_COARSE_LOCATION,
54 AppOpsManager.OP_FINE_LOCATION)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050055 val intents = listOf(Intent.ACTION_USER_FOREGROUND,
56 Intent.ACTION_MANAGED_PROFILE_ADDED,
57 Intent.ACTION_MANAGED_PROFILE_REMOVED)
58 const val TAG = "PrivacyItemController"
Fabian Kozynski508422b2018-12-20 10:58:17 -050059 const val SYSTEM_UID = 1000
Fabian Kozynski8d06c712018-11-07 10:33:02 -050060 }
Fabian Kozynski508422b2018-12-20 10:58:17 -050061
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050062 @VisibleForTesting
63 internal var privacyList = emptyList<PrivacyItem>()
64 get() = field.toList() // Provides a shallow copy of the list
65
Fabian Kozynski8d06c712018-11-07 10:33:02 -050066 private val userManager = context.getSystemService(UserManager::class.java)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050067 private var currentUserIds = emptyList<Int>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050068 private var listening = false
Fabian Kozynski0d03da32019-01-28 10:35:28 -050069 val systemApp =
70 PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050071 private val callbacks = mutableListOf<WeakReference<Callback>>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050072
Fabian Kozynski8d06c712018-11-07 10:33:02 -050073 private val notifyChanges = Runnable {
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050074 callbacks.forEach { it.get()?.privacyChanged(privacyList) }
Fabian Kozynski8d06c712018-11-07 10:33:02 -050075 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050076
Fabian Kozynski8d06c712018-11-07 10:33:02 -050077 private val updateListAndNotifyChanges = Runnable {
78 updatePrivacyList()
79 uiHandler.post(notifyChanges)
80 }
81
Fabian Kozynski8d06c712018-11-07 10:33:02 -050082 private val cb = object : AppOpsController.Callback {
83 override fun onActiveStateChanged(
84 code: Int,
85 uid: Int,
86 packageName: String,
87 active: Boolean
88 ) {
89 val userId = UserHandle.getUserId(uid)
90 if (userId in currentUserIds) {
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050091 update(false)
Fabian Kozynski8d06c712018-11-07 10:33:02 -050092 }
93 }
94 }
95
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050096 @VisibleForTesting
97 internal var userSwitcherReceiver = Receiver()
98 set(value) {
99 context.unregisterReceiver(field)
100 field = value
101 registerReceiver()
102 }
103
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500104 private fun unregisterReceiver() {
105 context.unregisterReceiver(userSwitcherReceiver)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500106 }
107
108 private fun registerReceiver() {
109 context.registerReceiverAsUser(userSwitcherReceiver, UserHandle.ALL, IntentFilter().apply {
110 intents.forEach {
111 addAction(it)
112 }
113 }, null, null)
114 }
115
116 private fun update(updateUsers: Boolean) {
117 if (updateUsers) {
118 val currentUser = ActivityManager.getCurrentUser()
119 currentUserIds = userManager.getProfiles(currentUser).map { it.id }
120 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500121 bgHandler.post(updateListAndNotifyChanges)
122 }
123
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500124 @VisibleForTesting
125 internal fun setListening(listen: Boolean) {
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500126 if (listening == listen) return
127 listening = listen
128 if (listening) {
129 appOpsController.addCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500130 registerReceiver()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500131 update(true)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500132 } else {
133 appOpsController.removeCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500134 unregisterReceiver()
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500135 }
136 }
137
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500138 private fun addCallback(callback: WeakReference<Callback>) {
139 callbacks.add(callback)
140 if (callbacks.isNotEmpty() && !listening) setListening(true)
141 // Notify this callback if we didn't set to listening
142 else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
143 }
144
145 private fun removeCallback(callback: WeakReference<Callback>) {
146 // Removes also if the callback is null
147 callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
148 if (callbacks.isEmpty()) setListening(false)
149 }
150
151 fun addCallback(callback: Callback) {
152 addCallback(WeakReference(callback))
153 }
154
155 fun removeCallback(callback: Callback) {
156 removeCallback(WeakReference(callback))
157 }
158
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500159 private fun updatePrivacyList() {
160 privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500161 .mapNotNull { toPrivacyItem(it) }.distinct()
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500162 }
163
164 private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
165 val type: PrivacyType = when (appOpItem.code) {
166 AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
167 AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
168 AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
169 AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
170 else -> return null
171 }
Fabian Kozynski508422b2018-12-20 10:58:17 -0500172 if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
Fabian Kozynski0d03da32019-01-28 10:35:28 -0500173 val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
Fabian Kozynskief124492018-11-02 11:02:11 -0400174 return PrivacyItem(type, app)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500175 }
176
177 // Used by containing class to get notified of changes
178 interface Callback {
179 fun privacyChanged(privacyItems: List<PrivacyItem>)
180 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500181
182 internal inner class Receiver : BroadcastReceiver() {
183 override fun onReceive(context: Context?, intent: Intent?) {
184 if (intent?.action in intents) {
185 update(true)
186 }
187 }
188 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500189
190 private class NotifyChangesToCallback(
191 private val callback: Callback?,
192 private val list: List<PrivacyItem>
193 ) : Runnable {
194 override fun run() {
195 callback?.privacyChanged(list)
196 }
197 }
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500198
199 override fun dump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
200 pw?.println("PrivacyItemController state:")
201 pw?.println(" Listening: $listening")
202 pw?.println(" Current user ids: $currentUserIds")
203 pw?.println(" Privacy Items:")
204 privacyList.forEach {
205 pw?.print(" ")
206 pw?.println(it.toString())
207 }
208 pw?.println(" Callbacks:")
209 callbacks.forEach {
210 it.get()?.let {
211 pw?.print(" ")
212 pw?.println(it.toString())
213 }
214 }
215 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500216}