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