blob: f1c3bf299eea92c2d371ac9f33cd5827af2bf274 [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 Kozynski8d06c712018-11-07 10:33:02 -050029import com.android.systemui.Dependency
30import com.android.systemui.appops.AppOpItem
31import com.android.systemui.appops.AppOpsController
Fabian Kozynski508422b2018-12-20 10:58:17 -050032import com.android.systemui.R
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050033import java.lang.ref.WeakReference
34import javax.inject.Inject
35import javax.inject.Singleton
Fabian Kozynski8d06c712018-11-07 10:33:02 -050036
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050037@Singleton
38class PrivacyItemController @Inject constructor(val context: Context) {
Fabian Kozynski8d06c712018-11-07 10:33:02 -050039
40 companion object {
41 val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
42 AppOpsManager.OP_RECORD_AUDIO,
43 AppOpsManager.OP_COARSE_LOCATION,
44 AppOpsManager.OP_FINE_LOCATION)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050045 val intents = listOf(Intent.ACTION_USER_FOREGROUND,
46 Intent.ACTION_MANAGED_PROFILE_ADDED,
47 Intent.ACTION_MANAGED_PROFILE_REMOVED)
48 const val TAG = "PrivacyItemController"
Fabian Kozynski508422b2018-12-20 10:58:17 -050049 const val SYSTEM_UID = 1000
Fabian Kozynski8d06c712018-11-07 10:33:02 -050050 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -050051 private var privacyList = emptyList<PrivacyItem>()
Fabian Kozynski508422b2018-12-20 10:58:17 -050052
Aurimas Liutikasf1f63f62019-01-03 13:00:58 -080053 @Suppress("DEPRECATION")
Fabian Kozynski8d06c712018-11-07 10:33:02 -050054 private val appOpsController = Dependency.get(AppOpsController::class.java)
55 private val userManager = context.getSystemService(UserManager::class.java)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050056 private var currentUserIds = emptyList<Int>()
Aurimas Liutikasf1f63f62019-01-03 13:00:58 -080057 @Suppress("DEPRECATION")
Fabian Kozynski8d06c712018-11-07 10:33:02 -050058 private val bgHandler = Handler(Dependency.get(Dependency.BG_LOOPER))
Aurimas Liutikasf1f63f62019-01-03 13:00:58 -080059 @Suppress("DEPRECATION")
Fabian Kozynski8d06c712018-11-07 10:33:02 -050060 private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050061 private var listening = false
Fabian Kozynski0d03da32019-01-28 10:35:28 -050062 val systemApp =
63 PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050064 private val callbacks = mutableListOf<WeakReference<Callback>>()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050065
Fabian Kozynski8d06c712018-11-07 10:33:02 -050066 private val notifyChanges = Runnable {
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050067 callbacks.forEach { it.get()?.privacyChanged(privacyList) }
Fabian Kozynski8d06c712018-11-07 10:33:02 -050068 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050069
Fabian Kozynski8d06c712018-11-07 10:33:02 -050070 private val updateListAndNotifyChanges = Runnable {
71 updatePrivacyList()
72 uiHandler.post(notifyChanges)
73 }
74
Fabian Kozynski8d06c712018-11-07 10:33:02 -050075 private val cb = object : AppOpsController.Callback {
76 override fun onActiveStateChanged(
77 code: Int,
78 uid: Int,
79 packageName: String,
80 active: Boolean
81 ) {
82 val userId = UserHandle.getUserId(uid)
83 if (userId in currentUserIds) {
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050084 update(false)
Fabian Kozynski8d06c712018-11-07 10:33:02 -050085 }
86 }
87 }
88
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050089 @VisibleForTesting
90 internal var userSwitcherReceiver = Receiver()
91 set(value) {
92 context.unregisterReceiver(field)
93 field = value
94 registerReceiver()
95 }
96
Fabian Kozynski04f83eb2019-01-22 10:38:40 -050097 private fun unregisterReceiver() {
98 context.unregisterReceiver(userSwitcherReceiver)
Fabian Kozynskib5625ac2018-11-21 08:56:55 -050099 }
100
101 private fun registerReceiver() {
102 context.registerReceiverAsUser(userSwitcherReceiver, UserHandle.ALL, IntentFilter().apply {
103 intents.forEach {
104 addAction(it)
105 }
106 }, null, null)
107 }
108
109 private fun update(updateUsers: Boolean) {
110 if (updateUsers) {
111 val currentUser = ActivityManager.getCurrentUser()
112 currentUserIds = userManager.getProfiles(currentUser).map { it.id }
113 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500114 bgHandler.post(updateListAndNotifyChanges)
115 }
116
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500117 @VisibleForTesting
118 internal fun setListening(listen: Boolean) {
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500119 if (listening == listen) return
120 listening = listen
121 if (listening) {
122 appOpsController.addCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500123 registerReceiver()
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500124 update(true)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500125 } else {
126 appOpsController.removeCallback(OPS, cb)
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500127 unregisterReceiver()
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500128 }
129 }
130
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500131 private fun addCallback(callback: WeakReference<Callback>) {
132 callbacks.add(callback)
133 if (callbacks.isNotEmpty() && !listening) setListening(true)
134 // Notify this callback if we didn't set to listening
135 else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
136 }
137
138 private fun removeCallback(callback: WeakReference<Callback>) {
139 // Removes also if the callback is null
140 callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
141 if (callbacks.isEmpty()) setListening(false)
142 }
143
144 fun addCallback(callback: Callback) {
145 addCallback(WeakReference(callback))
146 }
147
148 fun removeCallback(callback: Callback) {
149 removeCallback(WeakReference(callback))
150 }
151
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500152 private fun updatePrivacyList() {
153 privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
Fabian Kozynski510585d2018-12-19 13:59:17 -0500154 .mapNotNull { toPrivacyItem(it) }.distinct()
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500155 }
156
157 private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
158 val type: PrivacyType = when (appOpItem.code) {
159 AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
160 AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
161 AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
162 AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
163 else -> return null
164 }
Fabian Kozynski508422b2018-12-20 10:58:17 -0500165 if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
Fabian Kozynski0d03da32019-01-28 10:35:28 -0500166 val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
Fabian Kozynskief124492018-11-02 11:02:11 -0400167 return PrivacyItem(type, app)
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500168 }
169
170 // Used by containing class to get notified of changes
171 interface Callback {
172 fun privacyChanged(privacyItems: List<PrivacyItem>)
173 }
Fabian Kozynskib5625ac2018-11-21 08:56:55 -0500174
175 internal inner class Receiver : BroadcastReceiver() {
176 override fun onReceive(context: Context?, intent: Intent?) {
177 if (intent?.action in intents) {
178 update(true)
179 }
180 }
181 }
Fabian Kozynski04f83eb2019-01-22 10:38:40 -0500182
183 private class NotifyChangesToCallback(
184 private val callback: Callback?,
185 private val list: List<PrivacyItem>
186 ) : Runnable {
187 override fun run() {
188 callback?.privacyChanged(list)
189 }
190 }
Fabian Kozynski8d06c712018-11-07 10:33:02 -0500191}