[Device Supervision] Implement createConfirmSupervisionCredentialsIntent API

The `ConfirmSupervisionCredentialsActivity` has been added and it's intended to be launched via the intent.

Bug: 392961554
Flag: android.app.supervision.flags.enable_supervision_settings_screen
Test: atest SupervisionMainSwitchPreferenceTest
Change-Id: I2322256a5711d5b90f826f467110c6861a7734ad
diff --git a/Android.bp b/Android.bp
index 150bdaf..c0a4c7c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -139,6 +139,7 @@
         "aconfig_settings_flags",
         "aconfig_settingslib_flags",
         "android.app.flags-aconfig",
+        "android.app.supervision.flags-aconfig",
         "android.provider.flags-aconfig",
         "android.security.flags-aconfig",
         "android.view.contentprotection.flags-aconfig",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8f5699c..90119a9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2817,6 +2817,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".supervision.ConfirmSupervisionCredentialsActivity"
+            android:exported="true"
+            android:featureFlag="android.app.supervision.flags.supervision_manager_apis">
+            <intent-filter>
+                <action android:name="android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".SetupRedactionInterstitial"
             android:enabled="false"
             android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 79020f9..104edfe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -14309,5 +14309,7 @@
     <!-- Title for web content filters browser category allow all sites option [CHAR LIMIT=60] -->
     <string name="supervision_web_content_filters_browser_allow_all_sites_title">Allow all sites</string>
     <!-- Generic content description that is attached to the preview illustration at the top of an Accessibility feature toggle page. [CHAR LIMIT=NONE] -->
+    <!-- Title for supervision PIN verification screen [CHAR LIMIT=60] -->
+    <string name="supervision_full_screen_pin_verification_title">Enter supervision PIN</string>
     <string name="accessibility_illustration_content_description"><xliff:g id="feature" example="Select to Speak">%1$s</xliff:g> animation</string>
 </resources>
diff --git a/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt b/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt
new file mode 100644
index 0000000..b459f53
--- /dev/null
+++ b/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt
@@ -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"
+    }
+}
diff --git a/src/com/android/settings/supervision/SupervisionDashboardScreen.kt b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
index 86f77f7..674c0f3a 100644
--- a/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
+++ b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
@@ -56,7 +56,7 @@
 
     override fun getPreferenceHierarchy(context: Context) =
         preferenceHierarchy(context, this) {
-            +SupervisionMainSwitchPreference()
+            +SupervisionMainSwitchPreference(context)
             +TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += {
                 +SupervisionWebContentFiltersScreen.KEY
             }
diff --git a/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
index 5a84137..88afc55 100644
--- a/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
+++ b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
@@ -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.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 @@
     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 @@
     ) {
         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 @@
                 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 @@
 
     companion object {
         const val KEY = "device_supervision_switch"
+        const val REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS = 0
     }
 }
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
index d5fa297..bf579d9 100644
--- a/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.settings.supervision
 
+import android.app.Activity
 import android.app.supervision.flags.Flags
 import android.content.Context
 import android.platform.test.annotations.DisableFlags
@@ -24,6 +25,7 @@
 import androidx.preference.Preference
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
 import com.android.settingslib.widget.MainSwitchPreference
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -57,7 +59,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
-    fun toggleMainSwitch_disablesChildPreferences() {
+    fun toggleMainSwitch_pinVerificationSucceeded_enablesChildPreferences() {
         FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
             fragment ->
             val mainSwitchPreference =
@@ -68,8 +70,38 @@
             assertThat(childPreference.isEnabled).isFalse()
 
             mainSwitchPreference.performClick()
+            // Pretend the PIN verification succeeded.
+            fragment.onActivityResult(
+                requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+                resultCode = Activity.RESULT_OK,
+                data = null,
+            )
 
             assertThat(childPreference.isEnabled).isTrue()
         }
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
+    fun toggleMainSwitch_pinVerificationFailed_childPreferencesRemainDisabled() {
+        FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
+            fragment ->
+            val mainSwitchPreference =
+                fragment.findPreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)!!
+            val childPreference =
+                fragment.findPreference<Preference>(SupervisionPinManagementScreen.KEY)!!
+
+            assertThat(childPreference.isEnabled).isFalse()
+
+            mainSwitchPreference.performClick()
+            // Pretend the PIN verification failed.
+            fragment.onActivityResult(
+                requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+                resultCode = Activity.RESULT_CANCELED,
+                data = null,
+            )
+
+            assertThat(childPreference.isEnabled).isFalse()
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
index 8b15c29..c7c393d 100644
--- a/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
@@ -15,25 +15,33 @@
  */
 package com.android.settings.supervision
 
+import android.app.Activity
 import android.app.supervision.SupervisionManager
 import android.content.Context
 import android.content.ContextWrapper
+import android.content.Intent
+import androidx.preference.Preference
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
+import com.android.settingslib.metadata.PreferenceLifecycleContext
 import com.android.settingslib.preference.createAndBindWidget
 import com.android.settingslib.widget.MainSwitchPreference
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
 import org.mockito.kotlin.stub
 import org.mockito.kotlin.verify
 
 @RunWith(AndroidJUnit4::class)
 class SupervisionMainSwitchPreferenceTest {
-    private val preference = SupervisionMainSwitchPreference()
-
+    private val mockLifeCycleContext = mock<PreferenceLifecycleContext>()
     private val mockSupervisionManager = mock<SupervisionManager>()
 
     private val appContext: Context = ApplicationProvider.getApplicationContext()
@@ -46,6 +54,13 @@
                 }
         }
 
+    private val preference = SupervisionMainSwitchPreference(context)
+
+    @Before
+    fun setUp() {
+        preference.onCreate(mockLifeCycleContext)
+    }
+
     @Test
     fun checked_supervisionEnabled_returnTrue() {
         setSupervisionEnabled(true)
@@ -61,7 +76,7 @@
     }
 
     @Test
-    fun toggleOn() {
+    fun toggleOn_triggersPinVerification() {
         setSupervisionEnabled(false)
         val widget = getMainSwitchPreference()
 
@@ -69,26 +84,90 @@
 
         widget.performClick()
 
+        verifyConfirmSupervisionCredentialsActivityStarted()
+        assertThat(widget.isChecked).isFalse()
+        verify(mockSupervisionManager, never()).setSupervisionEnabled(false)
+    }
+
+    @Test
+    fun toggleOn_pinVerificationSucceeded_supervisionEnabled() {
+        setSupervisionEnabled(false)
+        val widget = getMainSwitchPreference()
+
+        assertThat(widget.isChecked).isFalse()
+
+        preference.onActivityResult(
+            mockLifeCycleContext,
+            REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+            Activity.RESULT_OK,
+            null,
+        )
+
         assertThat(widget.isChecked).isTrue()
         verify(mockSupervisionManager).setSupervisionEnabled(true)
     }
 
     @Test
-    fun toggleOff() {
+    fun toggleOff_pinVerificationSucceeded_supervisionDisabled() {
         setSupervisionEnabled(true)
         val widget = getMainSwitchPreference()
 
         assertThat(widget.isChecked).isTrue()
 
-        widget.performClick()
+        preference.onActivityResult(
+            mockLifeCycleContext,
+            REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+            Activity.RESULT_OK,
+            null,
+        )
 
         assertThat(widget.isChecked).isFalse()
         verify(mockSupervisionManager).setSupervisionEnabled(false)
     }
 
-    private fun getMainSwitchPreference(): MainSwitchPreference =
-        preference.createAndBindWidget(context)
+    @Test
+    fun toggleOff_pinVerificationFailed_supervisionNotEnabled() {
+        setSupervisionEnabled(true)
+        val widget = getMainSwitchPreference()
+
+        assertThat(widget.isChecked).isTrue()
+
+        preference.onActivityResult(
+            mockLifeCycleContext,
+            REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+            Activity.RESULT_CANCELED,
+            null,
+        )
+
+        assertThat(widget.isChecked).isTrue()
+        verify(mockSupervisionManager, never()).setSupervisionEnabled(true)
+    }
 
     private fun setSupervisionEnabled(enabled: Boolean) =
         mockSupervisionManager.stub { on { isSupervisionEnabled } doReturn enabled }
+
+    private fun getMainSwitchPreference(): MainSwitchPreference {
+        val widget: MainSwitchPreference = preference.createAndBindWidget(context)
+
+        mockLifeCycleContext.stub {
+            on { findPreference<Preference>(SupervisionMainSwitchPreference.KEY) } doReturn widget
+            on {
+                requirePreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)
+            } doReturn widget
+        }
+        return widget
+    }
+
+    private fun verifyConfirmSupervisionCredentialsActivityStarted() {
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(mockLifeCycleContext)
+            .startActivityForResult(
+                intentCaptor.capture(),
+                eq(REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS),
+                eq(null),
+            )
+        assertThat(intentCaptor.allValues.size).isEqualTo(1)
+        assertThat(intentCaptor.firstValue.component?.className)
+            .isEqualTo(ConfirmSupervisionCredentialsActivity::class.java.name)
+    }
 }