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 }