• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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