Loading src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt +67 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, Loading @@ -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) { Loading Loading @@ -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() } } tests/robotests/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivityTest.kt +50 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 } } Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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 = Loading Loading
src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt +67 −18 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, Loading @@ -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) { Loading Loading @@ -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() } }
tests/robotests/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivityTest.kt +50 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 } } Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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 = Loading