Merge "[Device Supervision] Implement createConfirmSupervisionCredentialsIntent API" into main
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settings.supervision
|
||||
|
||||
import android.Manifest.permission.USE_BIOMETRIC
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.biometrics.BiometricManager
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
|
||||
import android.os.Bundle
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.android.settings.R
|
||||
|
||||
/**
|
||||
* Activity for confirming supervision credentials using device credential authentication.
|
||||
*
|
||||
* This activity displays an authentication prompt to the user, requiring them to authenticate using
|
||||
* their device credentials (PIN, pattern, or password). It is specifically designed for verifying
|
||||
* credentials for supervision purposes.
|
||||
*
|
||||
* It returns `Activity.RESULT_OK` if authentication succeeds, and `Activity.RESULT_CANCELED` if
|
||||
* authentication fails or is canceled by the user.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Start this activity using `startActivityForResult()`.
|
||||
* 2. Handle the result in `onActivityResult()`.
|
||||
*
|
||||
* Permissions:
|
||||
* - Requires `android.permission.USE_BIOMETRIC`.
|
||||
*/
|
||||
class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
|
||||
private val mAuthenticationCallback =
|
||||
object : AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
Log.w(TAG, "onAuthenticationError(errorCode=$errorCode, errString=$errString)")
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(USE_BIOMETRIC)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// TODO(b/392961554): Check if caller is the SYSTEM_SUPERVISION role holder. Call
|
||||
// RoleManager#getRoleHolders(SYSTEM_SUPERVISION) and check if getCallingPackage() is in the
|
||||
// list.
|
||||
if (checkCallingOrSelfPermission(USE_BIOMETRIC) == PackageManager.PERMISSION_GRANTED) {
|
||||
showBiometricPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(USE_BIOMETRIC)
|
||||
fun showBiometricPrompt() {
|
||||
// TODO(b/392961554): adapts to new user profile type to trigger PIN verification dialog.
|
||||
val biometricPrompt =
|
||||
BiometricPrompt.Builder(this)
|
||||
.setTitle(getString(R.string.supervision_full_screen_pin_verification_title))
|
||||
.setConfirmationRequired(true)
|
||||
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
||||
.build()
|
||||
biometricPrompt.authenticate(
|
||||
CancellationSignal(),
|
||||
ContextCompat.getMainExecutor(this),
|
||||
mAuthenticationCallback,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// TODO(b/392961554): remove this tag and use shared tag after http://ag/31997167 is
|
||||
// submitted.
|
||||
const val TAG = "SupervisionSettings"
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class SupervisionDashboardScreen : PreferenceScreenCreator {
|
||||
|
||||
override fun getPreferenceHierarchy(context: Context) =
|
||||
preferenceHierarchy(context, this) {
|
||||
+SupervisionMainSwitchPreference()
|
||||
+SupervisionMainSwitchPreference(context)
|
||||
+TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += {
|
||||
+SupervisionWebContentFiltersScreen.KEY
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package com.android.settings.supervision
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.supervision.SupervisionManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.preference.Preference
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.datastore.KeyValueStore
|
||||
@@ -32,19 +34,22 @@ import com.android.settingslib.preference.MainSwitchPreferenceBinding
|
||||
import com.android.settingslib.preference.forEachRecursively
|
||||
|
||||
/** Main toggle to enable or disable device supervision. */
|
||||
class SupervisionMainSwitchPreference :
|
||||
class SupervisionMainSwitchPreference(context: Context) :
|
||||
MainSwitchPreference(KEY, R.string.device_supervision_switch_title),
|
||||
PreferenceSummaryProvider,
|
||||
MainSwitchPreferenceBinding,
|
||||
Preference.OnPreferenceChangeListener,
|
||||
PreferenceLifecycleProvider {
|
||||
|
||||
private val supervisionMainSwitchStorage = SupervisionMainSwitchStorage(context)
|
||||
private lateinit var lifeCycleContext: PreferenceLifecycleContext
|
||||
|
||||
// TODO(b/383568136): Make presence of summary conditional on whether PIN
|
||||
// has been set up before or not.
|
||||
override fun getSummary(context: Context): CharSequence? =
|
||||
context.getString(R.string.device_supervision_switch_no_pin_summary)
|
||||
|
||||
override fun storage(context: Context): KeyValueStore = SupervisionMainSwitchStorage(context)
|
||||
override fun storage(context: Context): KeyValueStore = supervisionMainSwitchStorage
|
||||
|
||||
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
|
||||
ReadWritePermit.DISALLOW
|
||||
@@ -55,26 +60,49 @@ class SupervisionMainSwitchPreference :
|
||||
override val sensitivityLevel: Int
|
||||
get() = SensitivityLevel.HIGH_SENSITIVITY
|
||||
|
||||
override fun onCreate(context: PreferenceLifecycleContext) {
|
||||
lifeCycleContext = context
|
||||
}
|
||||
|
||||
override fun onResume(context: PreferenceLifecycleContext) {
|
||||
updateDependentPreferencesEnabledState(
|
||||
context.findPreference<Preference>(KEY),
|
||||
supervisionMainSwitchStorage.getBoolean(KEY)!!,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onActivityResult(
|
||||
context: PreferenceLifecycleContext,
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
): Boolean {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val mainSwitchPreference =
|
||||
context.requirePreference<com.android.settingslib.widget.MainSwitchPreference>(KEY)
|
||||
val newValue = !supervisionMainSwitchStorage.getBoolean(KEY)!!
|
||||
mainSwitchPreference.setChecked(newValue)
|
||||
updateDependentPreferencesEnabledState(mainSwitchPreference, newValue)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
|
||||
super.bind(preference, metadata)
|
||||
preference.onPreferenceChangeListener = this
|
||||
}
|
||||
|
||||
override fun onResume(context: PreferenceLifecycleContext) {
|
||||
val currentValue = storage(context.applicationContext)?.getBoolean(key) ?: false
|
||||
|
||||
updateDependentPreferencesEnabledState(
|
||||
context.findPreference<Preference>(KEY),
|
||||
currentValue,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
|
||||
if (newValue !is Boolean) return true
|
||||
|
||||
updateDependentPreferencesEnabledState(preference, newValue)
|
||||
|
||||
return true
|
||||
val intent = Intent(lifeCycleContext, ConfirmSupervisionCredentialsActivity::class.java)
|
||||
lifeCycleContext.startActivityForResult(
|
||||
intent,
|
||||
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
|
||||
null,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
private fun updateDependentPreferencesEnabledState(
|
||||
@@ -83,9 +111,8 @@ class SupervisionMainSwitchPreference :
|
||||
) {
|
||||
preference?.parent?.forEachRecursively {
|
||||
if (
|
||||
it.parent?.key?.toString() ==
|
||||
SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
|
||||
it.key?.toString() == SupervisionPinManagementScreen.KEY
|
||||
it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
|
||||
it.key == SupervisionPinManagementScreen.KEY
|
||||
) {
|
||||
it.isEnabled = isChecked
|
||||
}
|
||||
@@ -103,7 +130,6 @@ class SupervisionMainSwitchPreference :
|
||||
as T
|
||||
|
||||
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
|
||||
// TODO(b/392694561): add PIN protection to main toggle.
|
||||
if (key == KEY && value is Boolean) {
|
||||
val supervisionManager = context.getSystemService(SupervisionManager::class.java)
|
||||
supervisionManager?.setSupervisionEnabled(value)
|
||||
@@ -113,5 +139,6 @@ class SupervisionMainSwitchPreference :
|
||||
|
||||
companion object {
|
||||
const val KEY = "device_supervision_switch"
|
||||
const val REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS = 0
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user