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.app.Application 20 import android.content.Intent 21 import android.hardware.SensorPrivacyManager 22 import android.hardware.SensorPrivacyManager.Sensors 23 import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE 24 import android.os.Build 25 import android.os.Process 26 import android.os.UserManager 27 import android.provider.DeviceConfig 28 import android.provider.Settings 29 import androidx.annotation.RequiresApi 30 import androidx.annotation.StringRes 31 import androidx.fragment.app.Fragment 32 import androidx.lifecycle.AndroidViewModel 33 import androidx.lifecycle.ViewModel 34 import androidx.lifecycle.ViewModelProvider 35 import com.android.permissioncontroller.R 36 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 37 import com.android.settingslib.RestrictedLockUtils 38 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin 39 40 /** Viewmodel for the privacy controls page. */ 41 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 42 class PrivacyControlsViewModel(private val app: Application) : AndroidViewModel(app) { 43 44 private val sensorPrivacyManager: SensorPrivacyManager = 45 app.getSystemService(SensorPrivacyManager::class.java)!! 46 private val userManager: UserManager = app.getSystemService(UserManager::class.java)!! 47 48 private val CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS = 49 app.getString(R.string.clipboard_show_access_notifications_config) 50 private val CONFIG_SHOW_ACCESS_NOTIFICATIONS_DEFAULT = 51 app.getString(R.string.show_access_notifications_default_config) 52 private val CONFIG_MIC_TOGGLE_ENABLED = app.getString(R.string.mic_toggle_enable_config) 53 private val CONFIG_CAMERA_TOGGLE_ENABLED = app.getString(R.string.camera_toggle_enable_config) 54 55 enum class Pref(val key: String, @StringRes val titleResId: Int) { 56 MIC("privacy_mic_toggle", R.string.mic_toggle_title), 57 CAMERA("privacy_camera_toggle", R.string.camera_toggle_title), 58 LOCATION("privacy_location_access", R.string.location_settings), 59 CLIPBOARD("show_clip_access_notification", R.string.show_clip_access_notification_title), 60 SHOW_PASSWORD("show_password", R.string.show_password_title); 61 62 companion object { <lambda>null63 @JvmStatic fun findByKey(inputKey: String) = values().find { it.key == inputKey } 64 } 65 } 66 67 data class PrefState(val visible: Boolean, val checked: Boolean, val admin: EnforcedAdmin?) 68 69 val controlStateLiveData: SmartUpdateMediatorLiveData<Map<Pref, PrefState>> = 70 object : 71 SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<Pref, PrefState>>(), 72 SensorPrivacyManager.OnSensorPrivacyChangedListener { 73 onUpdatenull74 override fun onUpdate() { 75 val shownPrefs = mutableMapOf<Pref, PrefState>() 76 shownPrefs[Pref.CAMERA] = 77 getSensorToggleState( 78 Sensors.CAMERA, 79 UserManager.DISALLOW_CAMERA_TOGGLE, 80 CONFIG_CAMERA_TOGGLE_ENABLED 81 ) 82 shownPrefs[Pref.MIC] = 83 getSensorToggleState( 84 Sensors.MICROPHONE, 85 UserManager.DISALLOW_MICROPHONE_TOGGLE, 86 CONFIG_MIC_TOGGLE_ENABLED 87 ) 88 shownPrefs[Pref.CLIPBOARD] = 89 PrefState(visible = true, checked = isClipboardEnabled(), admin = null) 90 shownPrefs[Pref.SHOW_PASSWORD] = 91 PrefState( 92 visible = shouldDisplayShowPasswordToggle(), 93 checked = isShowPasswordEnabled(), 94 admin = null 95 ) 96 value = shownPrefs 97 } 98 onActivenull99 override fun onActive() { 100 sensorPrivacyManager.addSensorPrivacyListener(this) 101 super.onActive() 102 update() 103 } 104 onInactivenull105 override fun onInactive() { 106 super.onInactive() 107 sensorPrivacyManager.removeSensorPrivacyListener(this) 108 } 109 110 @Suppress("OVERRIDE_DEPRECATION") onSensorPrivacyChangednull111 override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) { 112 update() 113 } 114 } 115 handlePrefClicknull116 fun handlePrefClick(fragment: Fragment, pref: Pref, admin: EnforcedAdmin?) { 117 when (pref) { 118 Pref.MIC -> toggleSensorOrShowAdmin(fragment, Sensors.MICROPHONE, admin) 119 Pref.CAMERA -> toggleSensorOrShowAdmin(fragment, Sensors.CAMERA, admin) 120 Pref.LOCATION -> goToLocation(fragment) 121 Pref.CLIPBOARD -> toggleClipboard() 122 Pref.SHOW_PASSWORD -> toggleShowPassword() 123 } 124 } 125 toggleSensorOrShowAdminnull126 private fun toggleSensorOrShowAdmin(fragment: Fragment, sensor: Int, admin: EnforcedAdmin?) { 127 if (admin != null) { 128 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(fragment.context, admin) 129 return 130 } 131 val blocked = sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor) 132 sensorPrivacyManager.setSensorPrivacy(sensor, !blocked) 133 } 134 goToLocationnull135 private fun goToLocation(fragment: Fragment) { 136 fragment.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) 137 } 138 getSensorToggleStatenull139 private fun getSensorToggleState( 140 sensor: Int, 141 restriction: String, 142 enableConfig: String 143 ): PrefState { 144 val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, Process.myUserHandle()) 145 val sensorConfigEnabled = 146 DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, enableConfig, true) 147 return PrefState( 148 visible = sensorConfigEnabled && sensorPrivacyManager.supportsSensorToggle(sensor), 149 checked = !sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor), 150 admin = 151 if ( 152 userManager 153 .getUserRestrictionSources(restriction, Process.myUserHandle()) 154 .isNotEmpty() 155 ) { 156 admin 157 } else { 158 null 159 } 160 ) 161 } 162 isClipboardEnablednull163 private fun isClipboardEnabled(): Boolean { 164 val clipboardDefaultEnabled = 165 DeviceConfig.getBoolean( 166 DeviceConfig.NAMESPACE_CLIPBOARD, 167 CONFIG_SHOW_ACCESS_NOTIFICATIONS_DEFAULT, 168 true 169 ) 170 val defaultSetting = if (clipboardDefaultEnabled) 1 else 0 171 return Settings.Secure.getInt( 172 app.contentResolver, 173 CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 174 defaultSetting 175 ) != 0 176 } 177 toggleClipboardnull178 private fun toggleClipboard() { 179 val newState = if (isClipboardEnabled()) 0 else 1 180 Settings.Secure.putInt( 181 app.contentResolver, 182 CONFIG_CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 183 newState 184 ) 185 } 186 isShowPasswordEnablednull187 private fun isShowPasswordEnabled(): Boolean { 188 return Settings.System.getInt(app.contentResolver, Settings.System.TEXT_SHOW_PASSWORD, 1) != 189 0 190 } 191 toggleShowPasswordnull192 private fun toggleShowPassword() { 193 Settings.System.putInt( 194 app.contentResolver, 195 Settings.System.TEXT_SHOW_PASSWORD, 196 if (isShowPasswordEnabled()) 0 else 1 197 ) 198 } 199 shouldDisplayShowPasswordTogglenull200 private fun shouldDisplayShowPasswordToggle(): Boolean { 201 return app.resources.getBoolean(R.bool.config_display_show_password_toggle) 202 } 203 } 204 205 /** 206 * Factory for a PrivacyControlsViewModel 207 * 208 * @param app The current application 209 */ 210 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 211 class PrivacyControlsViewModelFactory( 212 private val app: Application, 213 ) : ViewModelProvider.Factory { createnull214 override fun <T : ViewModel> create(modelClass: Class<T>): T { 215 @Suppress("UNCHECKED_CAST") return PrivacyControlsViewModel(app) as T 216 } 217 } 218