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.permissioncontroller.safetycenter.ui.model 18 19 import android.annotation.SuppressLint 20 import android.app.Application 21 import android.content.ClipboardManager 22 import android.content.Intent 23 import android.hardware.SensorPrivacyManager 24 import android.hardware.SensorPrivacyManager.Sensors 25 import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE 26 import android.os.Build 27 import android.os.Process 28 import android.os.UserManager 29 import android.provider.DeviceConfig 30 import android.provider.Settings 31 import androidx.annotation.RequiresApi 32 import androidx.annotation.StringRes 33 import androidx.fragment.app.Fragment 34 import androidx.lifecycle.AndroidViewModel 35 import androidx.lifecycle.ViewModel 36 import androidx.lifecycle.ViewModelProvider 37 import com.android.modules.utils.build.SdkLevel 38 import com.android.permission.flags.Flags 39 import com.android.permissioncontroller.R 40 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 41 import com.android.settingslib.RestrictedLockUtils 42 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin 43 44 /** Viewmodel for the privacy controls page. */ 45 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 46 // Suppress warnings related to the camera/mic privacy and clipboard privacy APIs. The PC has the 47 // permissions. 48 @SuppressLint("MissingPermission") 49 class PrivacyControlsViewModel(private val app: Application) : AndroidViewModel(app) { 50 51 private val sensorPrivacyManager: SensorPrivacyManager = 52 app.getSystemService(SensorPrivacyManager::class.java)!! 53 private val clipboardManager: ClipboardManager = 54 app.getSystemService(ClipboardManager::class.java)!! 55 private val userManager: UserManager = app.getSystemService(UserManager::class.java)!! 56 57 private val CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS = 58 app.getString(R.string.clipboard_show_access_notifications_config) 59 private val CONFIG_SHOW_ACCESS_NOTIFICATIONS_DEFAULT = 60 app.getString(R.string.show_access_notifications_default_config) 61 private val CONFIG_MIC_TOGGLE_ENABLED = app.getString(R.string.mic_toggle_enable_config) 62 private val CONFIG_CAMERA_TOGGLE_ENABLED = app.getString(R.string.camera_toggle_enable_config) 63 64 enum class Pref(val key: String, @StringRes val titleResId: Int) { 65 MIC("privacy_mic_toggle", R.string.mic_toggle_title), 66 CAMERA("privacy_camera_toggle", R.string.camera_toggle_title), 67 LOCATION("privacy_location_access", R.string.location_settings), 68 CLIPBOARD("show_clip_access_notification", R.string.show_clip_access_notification_title), 69 SHOW_PASSWORD("show_password", R.string.show_password_title); 70 71 companion object { <lambda>null72 @JvmStatic fun findByKey(inputKey: String) = values().find { it.key == inputKey } 73 } 74 } 75 76 data class PrefState(val visible: Boolean, val checked: Boolean, val admin: EnforcedAdmin?) 77 78 val controlStateLiveData: SmartUpdateMediatorLiveData<Map<Pref, PrefState>> = 79 object : 80 SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<Pref, PrefState>>(), 81 SensorPrivacyManager.OnSensorPrivacyChangedListener { 82 onUpdatenull83 override fun onUpdate() { 84 val shownPrefs = mutableMapOf<Pref, PrefState>() 85 shownPrefs[Pref.CAMERA] = 86 getSensorToggleState( 87 Sensors.CAMERA, 88 UserManager.DISALLOW_CAMERA_TOGGLE, 89 CONFIG_CAMERA_TOGGLE_ENABLED 90 ) 91 shownPrefs[Pref.MIC] = 92 getSensorToggleState( 93 Sensors.MICROPHONE, 94 UserManager.DISALLOW_MICROPHONE_TOGGLE, 95 CONFIG_MIC_TOGGLE_ENABLED 96 ) 97 shownPrefs[Pref.CLIPBOARD] = 98 PrefState(visible = true, checked = isClipboardEnabled(), admin = null) 99 shownPrefs[Pref.SHOW_PASSWORD] = 100 PrefState( 101 visible = shouldDisplayShowPasswordToggle(), 102 checked = isShowPasswordEnabled(), 103 admin = null 104 ) 105 value = shownPrefs 106 } 107 onActivenull108 override fun onActive() { 109 sensorPrivacyManager.addSensorPrivacyListener(this) 110 super.onActive() 111 update() 112 } 113 onInactivenull114 override fun onInactive() { 115 super.onInactive() 116 sensorPrivacyManager.removeSensorPrivacyListener(this) 117 } 118 119 @Suppress("OVERRIDE_DEPRECATION") onSensorPrivacyChangednull120 override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) { 121 update() 122 } 123 } 124 handlePrefClicknull125 fun handlePrefClick(fragment: Fragment, pref: Pref, admin: EnforcedAdmin?) { 126 when (pref) { 127 Pref.MIC -> toggleSensorOrShowAdmin(fragment, Sensors.MICROPHONE, admin) 128 Pref.CAMERA -> toggleSensorOrShowAdmin(fragment, Sensors.CAMERA, admin) 129 Pref.LOCATION -> goToLocation(fragment) 130 Pref.CLIPBOARD -> toggleClipboard() 131 Pref.SHOW_PASSWORD -> toggleShowPassword() 132 } 133 } 134 toggleSensorOrShowAdminnull135 private fun toggleSensorOrShowAdmin(fragment: Fragment, sensor: Int, admin: EnforcedAdmin?) { 136 if (admin != null) { 137 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(fragment.context, admin) 138 return 139 } 140 val blocked = sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor) 141 sensorPrivacyManager.setSensorPrivacy(sensor, !blocked) 142 } 143 goToLocationnull144 private fun goToLocation(fragment: Fragment) { 145 fragment.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) 146 } 147 getSensorToggleStatenull148 private fun getSensorToggleState( 149 sensor: Int, 150 restriction: String, 151 enableConfig: String 152 ): PrefState { 153 val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, Process.myUserHandle()) 154 val sensorConfigEnabled = 155 DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, enableConfig, true) 156 return PrefState( 157 visible = sensorConfigEnabled && sensorPrivacyManager.supportsSensorToggle(sensor), 158 checked = !sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor), 159 admin = 160 if ( 161 userManager 162 .getUserRestrictionSources(restriction, Process.myUserHandle()) 163 .isNotEmpty() 164 ) { 165 admin 166 } else { 167 null 168 } 169 ) 170 } 171 isClipboardEnablednull172 private fun isClipboardEnabled(): Boolean { 173 if (SdkLevel.isAtLeastV()) { 174 return clipboardManager.areClipboardAccessNotificationsEnabled() 175 } 176 177 val clipboardDefaultEnabled = 178 DeviceConfig.getBoolean( 179 DeviceConfig.NAMESPACE_CLIPBOARD, 180 CONFIG_SHOW_ACCESS_NOTIFICATIONS_DEFAULT, 181 true 182 ) 183 val defaultSetting = if (clipboardDefaultEnabled) 1 else 0 184 return Settings.Secure.getInt( 185 app.contentResolver, 186 CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 187 defaultSetting 188 ) != 0 189 } 190 toggleClipboardnull191 private fun toggleClipboard() { 192 if (SdkLevel.isAtLeastV()) { 193 clipboardManager.setClipboardAccessNotificationsEnabled(!isClipboardEnabled()) 194 return 195 } 196 197 val newState = if (isClipboardEnabled()) 0 else 1 198 Settings.Secure.putInt( 199 app.contentResolver, 200 CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 201 newState 202 ) 203 } 204 isShowPasswordEnablednull205 private fun isShowPasswordEnabled(): Boolean { 206 return Settings.System.getInt(app.contentResolver, Settings.System.TEXT_SHOW_PASSWORD, 1) != 207 0 208 } 209 toggleShowPasswordnull210 private fun toggleShowPassword() { 211 Settings.System.putInt( 212 app.contentResolver, 213 Settings.System.TEXT_SHOW_PASSWORD, 214 if (isShowPasswordEnabled()) 0 else 1 215 ) 216 } 217 shouldDisplayShowPasswordTogglenull218 private fun shouldDisplayShowPasswordToggle(): Boolean { 219 return app.resources.getBoolean(R.bool.config_display_show_password_toggle) 220 } 221 } 222 223 /** 224 * Factory for a PrivacyControlsViewModel 225 * 226 * @param app The current application 227 */ 228 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 229 class PrivacyControlsViewModelFactory( 230 private val app: Application, 231 ) : ViewModelProvider.Factory { createnull232 override fun <T : ViewModel> create(modelClass: Class<T>): T { 233 @Suppress("UNCHECKED_CAST") return PrivacyControlsViewModel(app) as T 234 } 235 } 236