• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.systemui.privacy
18 
19 import com.android.internal.annotations.VisibleForTesting
20 import com.android.systemui.Dumpable
21 import com.android.systemui.appops.AppOpsController
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Background
24 import com.android.systemui.dagger.qualifiers.Main
25 import com.android.systemui.dump.DumpManager
26 import com.android.systemui.privacy.logging.PrivacyLogger
27 import com.android.systemui.util.asIndenting
28 import com.android.systemui.util.concurrency.DelayableExecutor
29 import com.android.systemui.util.time.SystemClock
30 import com.android.systemui.util.withIncreasedIndent
31 import java.io.PrintWriter
32 import java.lang.ref.WeakReference
33 import java.util.concurrent.Executor
34 import javax.inject.Inject
35 
36 @SysUISingleton
37 class PrivacyItemController @Inject constructor(
38     @Main uiExecutor: DelayableExecutor,
39     @Background private val bgExecutor: DelayableExecutor,
40     private val privacyConfig: PrivacyConfig,
41     private val privacyItemMonitors: Set<@JvmSuppressWildcards PrivacyItemMonitor>,
42     private val logger: PrivacyLogger,
43     private val systemClock: SystemClock,
44     dumpManager: DumpManager
45 ) : Dumpable {
46 
47     @VisibleForTesting
48     internal companion object {
49         const val TAG = "PrivacyItemController"
50         @VisibleForTesting const val TIME_TO_HOLD_INDICATORS = 5000L
51     }
52 
53     @VisibleForTesting
54     internal var privacyList = emptyList<PrivacyItem>()
55         @Synchronized get() = field.toList() // Returns a shallow copy of the list
56         @Synchronized set
57 
58     private var listening = false
59     private val callbacks = mutableListOf<WeakReference<Callback>>()
60     private val internalUiExecutor = MyExecutor(uiExecutor)
61     private var holdingRunnableCanceler: Runnable? = null
62 
63     val micCameraAvailable
64         get() = privacyConfig.micCameraAvailable
65     val locationAvailable
66         get() = privacyConfig.locationAvailable
67     val allIndicatorsAvailable
68         get() = micCameraAvailable && locationAvailable && privacyConfig.mediaProjectionAvailable
69 
<lambda>null70     private val notifyChanges = Runnable {
71         val list = privacyList
72         callbacks.forEach { it.get()?.onPrivacyItemsChanged(list) }
73     }
74 
<lambda>null75     private val updateListAndNotifyChanges = Runnable {
76         updatePrivacyList()
77         uiExecutor.execute(notifyChanges)
78     }
79 
80     private val optionsCallback = object : PrivacyConfig.Callback {
onFlagLocationChangednull81         override fun onFlagLocationChanged(flag: Boolean) {
82             callbacks.forEach { it.get()?.onFlagLocationChanged(flag) }
83         }
84 
onFlagMicCameraChangednull85         override fun onFlagMicCameraChanged(flag: Boolean) {
86             callbacks.forEach { it.get()?.onFlagMicCameraChanged(flag) }
87         }
88 
onFlagMediaProjectionChangednull89         override fun onFlagMediaProjectionChanged(flag: Boolean) {
90             callbacks.forEach { it.get()?.onFlagMediaProjectionChanged(flag) }
91         }
92     }
93 
94     private val privacyItemMonitorCallback = object : PrivacyItemMonitor.Callback {
onPrivacyItemsChangednull95         override fun onPrivacyItemsChanged() {
96             update()
97         }
98     }
99 
100     init {
101         dumpManager.registerDumpable(TAG, this)
102         privacyConfig.addCallback(optionsCallback)
103     }
104 
updatenull105     private fun update() {
106         bgExecutor.execute {
107             updateListAndNotifyChanges.run()
108         }
109     }
110 
111     /**
112      * Updates listening status based on whether there are callbacks and the indicators are enabled.
113      *
114      * Always listen to all OPS so we don't have to figure out what we should be listening to. We
115      * still have to filter anyway. Updates are filtered in the callback.
116      *
117      * This is only called from private (add/remove)Callback and from the config listener, all in
118      * main thread.
119      */
setListeningStatenull120     private fun setListeningState() {
121         val listen = callbacks.isNotEmpty()
122         if (listening == listen) return
123         listening = listen
124         if (listening) {
125             privacyItemMonitors.forEach { it.startListening(privacyItemMonitorCallback) }
126             update()
127         } else {
128             privacyItemMonitors.forEach { it.stopListening() }
129             // Make sure that we remove all indicators and notify listeners if we are not
130             // listening anymore due to indicators being disabled
131             update()
132         }
133     }
134 
addCallbacknull135     private fun addCallback(callback: WeakReference<Callback>) {
136         callbacks.add(callback)
137         if (callbacks.isNotEmpty() && !listening) {
138             internalUiExecutor.updateListeningState()
139         }
140         // Notify this callback if we didn't set to listening
141         else if (listening) {
142             internalUiExecutor.execute(NotifyChangesToCallback(callback.get(), privacyList))
143         }
144     }
145 
removeCallbacknull146     private fun removeCallback(callback: WeakReference<Callback>) {
147         // Removes also if the callback is null
148         callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
149         if (callbacks.isEmpty()) {
150             internalUiExecutor.updateListeningState()
151         }
152     }
153 
addCallbacknull154     fun addCallback(callback: Callback) {
155         addCallback(WeakReference(callback))
156     }
157 
removeCallbacknull158     fun removeCallback(callback: Callback) {
159         removeCallback(WeakReference(callback))
160     }
161 
updatePrivacyListnull162     private fun updatePrivacyList() {
163         holdingRunnableCanceler?.run()?.also {
164             holdingRunnableCanceler = null
165         }
166         if (!listening) {
167             privacyList = emptyList()
168             return
169         }
170         val list = privacyItemMonitors.flatMap { it.getActivePrivacyItems() }.distinct()
171         privacyList = processNewList(list)
172     }
173 
174     /**
175      * Figure out which items have not been around for long enough and put them back in the list.
176      *
177      * Also schedule when we should check again to remove expired items. Because we always retrieve
178      * the current list, we have the latest info.
179      *
180      * @param list map of list retrieved from [AppOpsController].
181      * @return a list that may have added items that should be kept for some time.
182      */
processNewListnull183     private fun processNewList(list: List<PrivacyItem>): List<PrivacyItem> {
184         logger.logRetrievedPrivacyItemsList(list)
185 
186         // Anything earlier than this timestamp can be removed
187         val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS
188         val mustKeep = privacyList.filter {
189             it.timeStampElapsed > removeBeforeTime && !(it isIn list)
190         }
191 
192         // There are items we must keep because they haven't been around for enough time.
193         if (mustKeep.isNotEmpty()) {
194             logger.logPrivacyItemsToHold(mustKeep)
195             val earliestTime = mustKeep.minByOrNull { it.timeStampElapsed }!!.timeStampElapsed
196 
197             // Update the list again when the earliest item should be removed.
198             val delay = earliestTime - removeBeforeTime
199             logger.logPrivacyItemsUpdateScheduled(delay)
200             holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay)
201         }
202         return list.filter { !it.paused } + mustKeep
203     }
204 
205     /**
206      * Ignores the paused status to determine if the element is in the list
207      */
isInnull208     private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean {
209         return list.any {
210             it.privacyType == privacyType &&
211                     it.application == application &&
212                     it.timeStampElapsed == timeStampElapsed
213         }
214     }
215 
216     interface Callback : PrivacyConfig.Callback {
onPrivacyItemsChangednull217         fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
218 
219         @JvmDefault
220         fun onFlagAllChanged(flag: Boolean) {}
221     }
222 
223     private class NotifyChangesToCallback(
224         private val callback: Callback?,
225         private val list: List<PrivacyItem>
226     ) : Runnable {
runnull227         override fun run() {
228             callback?.onPrivacyItemsChanged(list)
229         }
230     }
231 
dumpnull232     override fun dump(pw: PrintWriter, args: Array<out String>) {
233         val ipw = pw.asIndenting()
234         ipw.println("PrivacyItemController state:")
235         ipw.withIncreasedIndent {
236             ipw.println("Listening: $listening")
237             ipw.println("Privacy Items:")
238             ipw.withIncreasedIndent {
239                 privacyList.forEach {
240                     ipw.println(it.toString())
241                 }
242             }
243 
244             ipw.println("Callbacks:")
245             ipw.withIncreasedIndent {
246                 callbacks.forEach {
247                     it.get()?.let {
248                         ipw.println(it.toString())
249                     }
250                 }
251             }
252 
253             ipw.println("PrivacyItemMonitors:")
254             ipw.withIncreasedIndent {
255                 privacyItemMonitors.forEach {
256                     it.dump(ipw, args)
257                 }
258             }
259         }
260         ipw.flush()
261     }
262 
263     private inner class MyExecutor(
264         private val delegate: DelayableExecutor
265     ) : Executor {
266 
267         private var listeningCanceller: Runnable? = null
268 
executenull269         override fun execute(command: Runnable) {
270             delegate.execute(command)
271         }
272 
updateListeningStatenull273         fun updateListeningState() {
274             listeningCanceller?.run()
275             listeningCanceller = delegate.executeDelayed({ setListeningState() }, 0L)
276         }
277     }
278 }