blob: 1c0974a0c98793fd5eea57280de2fa0693c673f7 [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>()
Fabian Kozynski72090102019-03-04 10:55:55 -050064 @Synchronized get() = field.toList() // Returns a shallow copy of the list
65 @Synchronized set
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -050066
Fabian Kozynski8d06c712018-11-07 10:33:02 -050067 private val userManager = context.getSystemService(UserManager::class.java)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050068 private var currentUserIds = emptyList<Int>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050069 private var listening = false
Fabian Kozynski0d03da32019-01-28 10:35:28 -050070 val systemApp =
71 PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050072 private val callbacks = mutableListOf<WeakReference<Callback>>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050073
Fabian Kozynski8d06c712018-11-07 10:33:02 -050074 private val notifyChanges = Runnable {
Fabian Kozynski72090102019-03-04 10:55:55 -050075 val list = privacyList
76 callbacks.forEach { it.get()?.privacyChanged(list) }
Fabian Kozynski8d06c712018-11-07 10:33:02 -050077 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050078
Fabian Kozynski8d06c712018-11-07 10:33:02 -050079 private val updateListAndNotifyChanges = Runnable {
80 updatePrivacyList()
81 uiHandler.post(notifyChanges)
82 }
83
Fabian Kozynski8d06c712018-11-07 10:33:02 -050084 private val cb = object : AppOpsController.Callback {
85 override fun onActiveStateChanged(
86 code: Int,
87 uid: Int,
88 packageName: String,
89 active: Boolean
90 ) {
91 val userId = UserHandle.getUserId(uid)
92 if (userId in currentUserIds) {
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050093 update(false)
Fabian Kozynski8d06c712018-11-07 10:33:02 -050094 }
95 }
96 }
97
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050098 @VisibleForTesting
99 internal var userSwitcherReceiver = Receiver()
100 set(value) {
101 context.unregisterReceiver(field)
102 field = value
103 registerReceiver()
104 }
105
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500106 private fun unregisterReceiver() {
107 context.unregisterReceiver(userSwitcherReceiver)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500108 }
109
110 private fun registerReceiver() {
111 context.registerReceiverAsUser(userSwitcherReceiver, UserHandle.ALL, IntentFilter().apply {
112 intents.forEach {
113 addAction(it)
114 }
115 }, null, null)
116 }
117
118 private fun update(updateUsers: Boolean) {
119 if (updateUsers) {
120 val currentUser = ActivityManager.getCurrentUser()
121 currentUserIds = userManager.getProfiles(currentUser).map { it.id }
122 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500123 bgHandler.post(updateListAndNotifyChanges)
124 }
125
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500126 @VisibleForTesting
127 internal fun setListening(listen: Boolean) {
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500128 if (listening == listen) return
129 listening = listen
130 if (listening) {
131 appOpsController.addCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500132 registerReceiver()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500133 update(true)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500134 } else {
135 appOpsController.removeCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500136 unregisterReceiver()
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500137 }
138 }
139
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500140 private fun addCallback(callback: WeakReference<Callback>) {
141 callbacks.add(callback)
142 if (callbacks.isNotEmpty() && !listening) setListening(true)
143 // Notify this callback if we didn't set to listening
144 else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
145 }
146
147 private fun removeCallback(callback: WeakReference<Callback>) {
148 // Removes also if the callback is null
149 callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
150 if (callbacks.isEmpty()) setListening(false)
151 }
152
153 fun addCallback(callback: Callback) {
154 addCallback(WeakReference(callback))
155 }
156
157 fun removeCallback(callback: Callback) {
158 removeCallback(WeakReference(callback))
159 }
160
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500161 private fun updatePrivacyList() {
Fabian Kozynski72090102019-03-04 10:55:55 -0500162
163 val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500164 .mapNotNull { toPrivacyItem(it) }.distinct()
Fabian Kozynski72090102019-03-04 10:55:55 -0500165 privacyList = list
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500166 }
167
168 private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
169 val type: PrivacyType = when (appOpItem.code) {
170 AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
171 AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
172 AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
173 AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
174 else -> return null
175 }
Fabian Kozynski508422b2018-12-20 10:58:17 -0500176 if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
Fabian Kozynski0d03da32019-01-28 10:35:28 -0500177 val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
Fabian Kozynskief124492018-11-02 11:02:11 -0400178 return PrivacyItem(type, app)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500179 }
180
181 // Used by containing class to get notified of changes
182 interface Callback {
183 fun privacyChanged(privacyItems: List<PrivacyItem>)
184 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500185
186 internal inner class Receiver : BroadcastReceiver() {
187 override fun onReceive(context: Context?, intent: Intent?) {
188 if (intent?.action in intents) {
189 update(true)
190 }
191 }
192 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500193
194 private class NotifyChangesToCallback(
195 private val callback: Callback?,
196 private val list: List<PrivacyItem>
197 ) : Runnable {
198 override fun run() {
199 callback?.privacyChanged(list)
200 }
201 }
Fabian Kozynskia6ff80b2019-02-12 11:32:44 -0500202
203 override fun dump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
204 pw?.println("PrivacyItemController state:")
205 pw?.println(" Listening: $listening")
206 pw?.println(" Current user ids: $currentUserIds")
207 pw?.println(" Privacy Items:")
208 privacyList.forEach {
209 pw?.print(" ")
210 pw?.println(it.toString())
211 }
212 pw?.println(" Callbacks:")
213 callbacks.forEach {
214 it.get()?.let {
215 pw?.print(" ")
216 pw?.println(it.toString())
217 }
218 }
219 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500220}