Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 731fe523 authored by juquan's avatar juquan
Browse files

[Device Supervision] Handle BiometricPrompt "more options" behavior to implement "Forgot PIN"

Bug: 398240353
Flag: android.app.supervision.flags.enable_supervision_settings_screen
Test: ConfirmSupervisionCredentialsActivityTest
Change-Id: I9be1802c79c437f511d3ab38effbed4b15a7a238
parent 6074e348
Loading
Loading
Loading
Loading
+67 −18
Original line number Diff line number Diff line
@@ -17,19 +17,26 @@ package com.android.settings.supervision

import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
import android.Manifest.permission.MANAGE_USERS
import android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED
import android.Manifest.permission.USE_BIOMETRIC_INTERNAL
import android.app.ActivityManager
import android.app.role.RoleManager
import android.app.supervision.SupervisionManager
import android.content.DialogInterface
import android.content.Intent
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.os.Binder
import android.os.Bundle
import android.os.CancellationSignal
import android.os.Process
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.OpenForTesting
import androidx.annotation.RequiresPermission
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.android.settings.R
@@ -80,42 +87,46 @@ open class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
            }
        }

    @RequiresPermission(anyOf = [INTERACT_ACROSS_USERS_FULL, MANAGE_USERS])
    private val supervisionPinRecoveryLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            setResult(result.resultCode)
            finish()
        }

    @RequiresPermission(
        allOf = [USE_BIOMETRIC_INTERNAL, SET_BIOMETRIC_DIALOG_ADVANCED, INTERACT_ACROSS_USERS_FULL]
    )
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!callerHasSupervisionRole() && !callerIsSystemUid()) {
            setResult(RESULT_CANCELED)
            finish()
            errorHandler()
            return
        }

        val supervisingUser = supervisingUserHandle
        if (supervisingUser == null) {
            Log.w(TAG, "No supervising user exists, cannot verify credentials.")
            setResult(RESULT_CANCELED)
            finish()
            errorHandler("No supervising user exists, cannot verify credentials.")
            return
        }

        if (!isSupervisingCredentialSet) {
            errorHandler("No supervising credential set, cannot verify credentials.")
            return
        }

        val activityManager = getSystemService(ActivityManager::class.java)
        if (!activityManager.startProfile(supervisingUser)) {
            Log.w(TAG, "Unable to start supervising user, cannot verify credentials.")
            setResult(RESULT_CANCELED)
            finish()
            errorHandler("Unable to start supervising user, cannot verify credentials.")
            return
        }

        showBiometricPrompt(supervisingUser.identifier)
    }

    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
    @RequiresPermission(allOf = [USE_BIOMETRIC_INTERNAL, SET_BIOMETRIC_DIALOG_ADVANCED])
    fun showBiometricPrompt(userId: Int) {
        val biometricPrompt =
            BiometricPrompt.Builder(this)
                .setTitle(getString(R.string.supervision_full_screen_pin_verification_title))
                .setConfirmationRequired(true)
                .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
                .build()
        biometricPrompt.authenticateUser(
        getBiometricPrompt()
            .authenticateUser(
                CancellationSignal(),
                ContextCompat.getMainExecutor(this),
                mAuthenticationCallback,
@@ -123,6 +134,38 @@ open class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
            )
    }

    @RequiresPermission(value = SET_BIOMETRIC_DIALOG_ADVANCED)
    @VisibleForTesting
    fun getBiometricPrompt(): BiometricPrompt {
        val builder =
            BiometricPrompt.Builder(this)
                .setTitle(getString(R.string.supervision_full_screen_pin_verification_title))
                .setConfirmationRequired(true)
                .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)

        val supportSuprevisionRecovery =
            getSystemService(SupervisionManager::class.java)?.getSupervisionRecoveryInfo()?.let {
                !it.email.isNullOrEmpty() || !it.id.isNullOrEmpty()
            } ?: false

        if (!supportSuprevisionRecovery) {
            return builder.build()
        }

        val intent =
            Intent(this, SupervisionPinRecoveryActivity::class.java).apply {
                action = SupervisionPinRecoveryActivity.ACTION_RECOVERY
            }
        val listener =
            DialogInterface.OnClickListener { _: DialogInterface?, _: Int ->
                supervisionPinRecoveryLauncher.launch(intent)
            }
        val moreOptionsButtonBuilder =
            PromptContentViewWithMoreOptionsButton.Builder()
                .setMoreOptionsButtonListener(ContextCompat.getMainExecutor(this), listener)
        return builder.setContentView(moreOptionsButtonBuilder.build()).build()
    }

    private fun callerHasSupervisionRole(): Boolean {
        val roleManager = getSystemService(RoleManager::class.java)
        if (roleManager == null) {
@@ -155,4 +198,10 @@ open class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
            Log.w(TAG, "Could not stop the supervising profile.")
        }
    }

    private fun errorHandler(errStr: String? = null) {
        errStr?.let { Log.w(TAG, it) }
        setResult(RESULT_CANCELED)
        finish()
    }
}
+50 −1
Original line number Diff line number Diff line
@@ -17,25 +17,33 @@ package com.android.settings.supervision

import android.app.Activity
import android.app.ActivityManager
import android.app.KeyguardManager
import android.app.role.RoleManager
import android.app.supervision.SupervisionManager
import android.app.supervision.SupervisionRecoveryInfo
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.os.Build
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING
import android.os.UserManager.USER_TYPE_PROFILE_TEST
import com.android.settings.R
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.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@@ -46,6 +54,8 @@ class ConfirmSupervisionCredentialsActivityTest {
    private val mockRoleManager = mock<RoleManager>()
    private val mockUserManager = mock<UserManager>()
    private val mockActivityManager = mock<ActivityManager>()
    private val mockKeyguardManager = mock<KeyguardManager>()
    private val mockSupervisionManager = mock<SupervisionManager>()

    private lateinit var mActivity: ConfirmSupervisionCredentialsActivity

@@ -60,6 +70,9 @@ class ConfirmSupervisionCredentialsActivityTest {
                on { getSystemService(RoleManager::class.java) } doReturn mockRoleManager
                on { getSystemService(UserManager::class.java) } doReturn mockUserManager
                on { getSystemService(ActivityManager::class.java) } doReturn mockActivityManager
                on { getSystemService(KeyguardManager::class.java) } doReturn mockKeyguardManager
                on { getSystemService(SupervisionManager::class.java) } doReturn
                    mockSupervisionManager
                on { callingPackage } doReturn callingPackage
            }
    }
@@ -69,6 +82,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        mockRoleManager.stub { on { getRoleHolders(any()) } doReturn listOf(callingPackage) }
        mockUserManager.stub { on { users } doReturn listOf(SUPERVISING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn true }
        mockKeyguardManager.stub { on { isDeviceSecure(SUPERVISING_USER_ID) } doReturn true }

        mActivity.onCreate(null)

@@ -85,6 +99,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        mockRoleManager.stub { on { getRoleHolders(any()) } doReturn listOf(callingPackage) }
        mockUserManager.stub { on { users } doReturn listOf(SUPERVISING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn false }
        mockKeyguardManager.stub { on { isDeviceSecure(SUPERVISING_USER_ID) } doReturn true }

        mActivity.onCreate(null)

@@ -98,6 +113,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        mockRoleManager.stub { on { getRoleHolders(any()) } doReturn listOf(otherPackage) }
        mockUserManager.stub { on { users } doReturn listOf(SUPERVISING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn true }
        mockKeyguardManager.stub { on { isDeviceSecure(SUPERVISING_USER_ID) } doReturn true }

        mActivity.onCreate(null)

@@ -111,6 +127,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        ShadowBinder.setCallingUid(Process.SYSTEM_UID)
        mockUserManager.stub { on { users } doReturn listOf(SUPERVISING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn true }
        mockKeyguardManager.stub { on { isDeviceSecure(SUPERVISING_USER_ID) } doReturn true }

        mActivity.onCreate(null)

@@ -123,6 +140,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        ShadowBinder.setCallingUid(Process.NOBODY_UID)
        mockUserManager.stub { on { users } doReturn listOf(SUPERVISING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn true }
        mockKeyguardManager.stub { on { isDeviceSecure(SUPERVISING_USER_ID) } doReturn true }

        mActivity.onCreate(null)

@@ -135,6 +153,7 @@ class ConfirmSupervisionCredentialsActivityTest {
        mockRoleManager.stub { on { getRoleHolders(any()) } doReturn listOf(callingPackage) }
        mockUserManager.stub { on { users } doReturn listOf(TESTING_USER_INFO) }
        mockActivityManager.stub { on { startProfile(any()) } doReturn true }
        mockKeyguardManager.stub { on { isDeviceSecure(TESTING_USER_ID) } doReturn false }

        mActivity.onCreate(null)

@@ -142,6 +161,36 @@ class ConfirmSupervisionCredentialsActivityTest {
        verify(mActivity).finish()
    }

    @Test
    fun getBiometricPrompt_recoveryEmailExist_showMoreOptionsButton() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "email" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

        val biometricPrompt = mActivity.getBiometricPrompt()

        assertThat(biometricPrompt.title)
            .isEqualTo(mActivity.getString(R.string.supervision_full_screen_pin_verification_title))
        assertThat(biometricPrompt.isConfirmationRequired).isTrue()
        assertThat(biometricPrompt.allowedAuthenticators)
            .isEqualTo(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
        assertThat(biometricPrompt.contentView)
            .isInstanceOf(PromptContentViewWithMoreOptionsButton::class.java)
    }

    fun getBiometricPrompt_recoveryInfoEmpty_noMoreOptionsButton() {
        whenever(mockSupervisionManager.supervisionRecoveryInfo)
            .thenReturn(SupervisionRecoveryInfo())

        val biometricPrompt = mActivity.getBiometricPrompt()

        assertThat(biometricPrompt.title)
            .isEqualTo(mActivity.getString(R.string.supervision_full_screen_pin_verification_title))
        assertThat(biometricPrompt.isConfirmationRequired).isTrue()
        assertThat(biometricPrompt.allowedAuthenticators)
            .isEqualTo(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
        assertThat(biometricPrompt.contentView).isNull()
    }

    private companion object {
        const val SUPERVISING_USER_ID = 5
        val SUPERVISING_USER_INFO =