1 /*
<lambda>null2  * Copyright (C) 2022 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 android.provider.DeviceConfig
20 import com.android.internal.annotations.VisibleForTesting
21 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
22 import com.android.systemui.Dumpable
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Main
25 import com.android.systemui.dump.DumpManager
26 import com.android.systemui.util.DeviceConfigProxy
27 import com.android.systemui.util.asIndenting
28 import com.android.systemui.util.annotations.WeaklyReferencedCallback
29 import com.android.systemui.util.concurrency.DelayableExecutor
30 import com.android.systemui.util.withIncreasedIndent
31 import java.io.PrintWriter
32 import java.lang.ref.WeakReference
33 import javax.inject.Inject
34 
35 @SysUISingleton
36 class PrivacyConfig @Inject constructor(
37     @Main private val uiExecutor: DelayableExecutor,
38     private val deviceConfigProxy: DeviceConfigProxy,
39     dumpManager: DumpManager
40 ) : Dumpable {
41 
42     @VisibleForTesting
43     internal companion object {
44         const val TAG = "PrivacyConfig"
45         private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
46         private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
47         private const val MEDIA_PROJECTION =
48                 SystemUiDeviceConfigFlags.PROPERTY_MEDIA_PROJECTION_INDICATORS_ENABLED
49         private const val DEFAULT_MIC_CAMERA = true
50         private const val DEFAULT_LOCATION = false
51         private const val DEFAULT_MEDIA_PROJECTION = true
52     }
53 
54     private val callbacks = mutableListOf<WeakReference<Callback>>()
55 
56     var micCameraAvailable = isMicCameraEnabled()
57         private set
58     var locationAvailable = isLocationEnabled()
59         private set
60     var mediaProjectionAvailable = isMediaProjectionEnabled()
61         private set
62 
63     private val devicePropertiesChangedListener =
64             DeviceConfig.OnPropertiesChangedListener { properties ->
65                 if (DeviceConfig.NAMESPACE_PRIVACY == properties.namespace) {
66                     // Running on the ui executor so can iterate on callbacks
67                     if (properties.keyset.contains(MIC_CAMERA)) {
68                         micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA)
69                         callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
70                     }
71 
72                     if (properties.keyset.contains(LOCATION)) {
73                         locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION)
74                         callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) }
75                     }
76 
77                     if (properties.keyset.contains(MEDIA_PROJECTION)) {
78                         mediaProjectionAvailable =
79                                 properties.getBoolean(MEDIA_PROJECTION, DEFAULT_MEDIA_PROJECTION)
80                         callbacks.forEach {
81                             it.get()?.onFlagMediaProjectionChanged(mediaProjectionAvailable)
82                         }
83                     }
84                 }
85             }
86 
87     init {
88         dumpManager.registerDumpable(TAG, this)
89         deviceConfigProxy.addOnPropertiesChangedListener(
90                 DeviceConfig.NAMESPACE_PRIVACY,
91                 uiExecutor,
92                 devicePropertiesChangedListener)
93     }
94 
95     private fun isMicCameraEnabled(): Boolean {
96         return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
97                 MIC_CAMERA, DEFAULT_MIC_CAMERA)
98     }
99 
100     private fun isLocationEnabled(): Boolean {
101         return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
102                 LOCATION, DEFAULT_LOCATION)
103     }
104 
105     private fun isMediaProjectionEnabled(): Boolean {
106         return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
107                 MEDIA_PROJECTION, DEFAULT_MEDIA_PROJECTION)
108     }
109 
110     fun addCallback(callback: Callback) {
111         addCallback(WeakReference(callback))
112     }
113 
114     fun removeCallback(callback: Callback) {
115         removeCallback(WeakReference(callback))
116     }
117 
118     private fun addCallback(callback: WeakReference<Callback>) {
119         uiExecutor.execute {
120             callbacks.add(callback)
121         }
122     }
123 
124     private fun removeCallback(callback: WeakReference<Callback>) {
125         uiExecutor.execute {
126             // Removes also if the callback is null
127             callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
128         }
129     }
130 
131     override fun dump(pw: PrintWriter, args: Array<out String>) {
132         val ipw = pw.asIndenting()
133         ipw.println("PrivacyConfig state:")
134         ipw.withIncreasedIndent {
135             ipw.println("micCameraAvailable: $micCameraAvailable")
136             ipw.println("locationAvailable: $locationAvailable")
137             ipw.println("mediaProjectionAvailable: $mediaProjectionAvailable")
138             ipw.println("Callbacks:")
139             ipw.withIncreasedIndent {
140                 callbacks.forEach { callback ->
141                     callback.get()?.let { ipw.println(it) }
142                 }
143             }
144         }
145         ipw.flush()
146     }
147 
148     @WeaklyReferencedCallback
149     interface Callback {
150         fun onFlagMicCameraChanged(flag: Boolean) {}
151 
152         fun onFlagLocationChanged(flag: Boolean) {}
153 
154         fun onFlagMediaProjectionChanged(flag: Boolean) {}
155     }
156 }