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

Commit d943d2fc authored by Sandy Pan's avatar Sandy Pan
Browse files

Use SupervisionRecoveryInfo parcelable as EXTRA

Bug: 416264895
Test: SupervisionPinRecoveryActivityTest
Flag: android.app.supervision.flags.enable_supervision_pin_recovery_screen
Change-Id: I3c97dacb742d65c3af3b51e166671c690f2a7349
parent 948cfe03
Loading
Loading
Loading
Loading
+21 −36
Original line number Diff line number Diff line
@@ -16,14 +16,11 @@
package com.android.settings.supervision

import android.Manifest
import android.app.Activity
import android.app.supervision.SupervisionManager
import android.app.supervision.SupervisionRecoveryInfo
import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING
import android.app.supervision.SupervisionRecoveryInfo.STATE_VERIFIED
import android.app.supervision.SupervisionRecoveryInfo.EXTRA_SUPERVISION_RECOVERY_INFO
import android.content.Intent
import android.os.Bundle
import android.os.PersistableBundle
import android.os.UserManager
import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING
import android.util.Log
@@ -31,7 +28,6 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresPermission
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.FragmentActivity
import com.android.settings.R
import com.android.settingslib.supervision.SupervisionIntentProvider
@@ -100,9 +96,7 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {

            recoveryIntent.apply {
                // Pass along any available recovery information.
                // TODO(b/409805806): will pass the parcelable once the system API is available.
                recoveryInfo?.accountName?.let { putExtra(EXTRA_RECOVERY_EMAIL, it) }
                recoveryInfo?.accountData?.getString("id")?.let { putExtra(EXTRA_RECOVERY_ID, it) }
                putExtra(EXTRA_SUPERVISION_RECOVERY_INFO, recoveryInfo)
                verificationLauncher.launch(this)
            }
        } else {
@@ -111,7 +105,7 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
    }

    private fun onPinConfirmed(resultCode: Int) {
        if (resultCode == Activity.RESULT_OK) {
        if (resultCode == RESULT_OK) {
            val nextAction = intent.action
            when (nextAction) {
                ACTION_SETUP_VERIFIED -> {
@@ -148,9 +142,7 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
                        val supervisionManager = getSystemService(SupervisionManager::class.java)
                        val recoveryInfo = supervisionManager?.getSupervisionRecoveryInfo()
                        postSetupVerifyIntent.apply {
                            // TODO(b/409805806): will use the parcelable once the system API is
                            // available.
                            recoveryInfo?.accountName?.let { putExtra(EXTRA_RECOVERY_EMAIL, it) }
                            putExtra(EXTRA_SUPERVISION_RECOVERY_INFO, recoveryInfo)
                            verificationLauncher.launch(postSetupVerifyIntent)
                        }
                    } else {
@@ -174,25 +166,22 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
                ACTION_SETUP_VERIFIED,
                ACTION_POST_SETUP_VERIFY -> {
                    if (data != null) {
                        val supervisionManager = getSystemService(SupervisionManager::class.java)
                        // TODO(b/409805806): will directly get the parcelable from intent once the
                        // system API is available.
                        val recoveryInfo =
                            data.getStringExtra(EXTRA_RECOVERY_EMAIL)?.let {
                                SupervisionRecoveryInfo(
                                    /* accountName */ it,
                                    /* accountType */ "default",
                                    /* state */ if (action == ACTION_SETUP) STATE_PENDING
                                    else STATE_VERIFIED,
                                    /* accountData */ PersistableBundle().apply {
                                        putString("id", data.getStringExtra(EXTRA_RECOVERY_ID))
                                    },
                            data.getParcelableExtra(
                                EXTRA_SUPERVISION_RECOVERY_INFO,
                                SupervisionRecoveryInfo::class.java,
                            )
                            }
                        if (recoveryInfo != null) {
                            val supervisionManager = getSystemService(SupervisionManager::class.java)
                            supervisionManager?.setSupervisionRecoveryInfo(recoveryInfo)
                            handleSuccess()
                        } else {
                        handleError("Cannot save recovery info, no recovery info from result.")
                            handleError(
                                "Cannot save recovery info, no valid recovery info from result."
                            )
                        }
                    } else {
                        handleError("Cannot save recovery info, no result data.")
                    }
                }
                else -> handleError("Unknown action after verification: $action")
@@ -205,7 +194,7 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
    }

    private fun onPinSet(resultCode: Int) {
        if (resultCode == Activity.RESULT_OK) {
        if (resultCode == RESULT_OK) {
            // After the new PIN being set.
            Toast.makeText(
                    this,
@@ -235,13 +224,13 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
    /** Helper method to handle errors consistently. */
    private fun handleError(errorMessage: String) {
        Log.e(SupervisionLog.TAG, errorMessage)
        setResult(Activity.RESULT_CANCELED)
        setResult(RESULT_CANCELED)
        finish()
    }

    /** Helper method to handle success consistently. */
    private fun handleSuccess() {
        setResult(Activity.RESULT_OK)
        setResult(RESULT_OK)
        finish()
    }

@@ -290,9 +279,5 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
            "android.app.supervision.action.SETUP_VERIFIED_PIN_RECOVERY"
        const val ACTION_POST_SETUP_VERIFY =
            "android.app.supervision.action.POST_SETUP_VERIFY_PIN_RECOVERY"

        // Extra keys
        @VisibleForTesting const val EXTRA_RECOVERY_EMAIL = "recoveryEmail"
        @VisibleForTesting const val EXTRA_RECOVERY_ID = "recoveryId"
    }
}
+93 −43
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ import android.app.Activity
import android.app.Application
import android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION
import android.app.supervision.SupervisionManager
import android.app.supervision.SupervisionRecoveryInfo
import android.app.supervision.SupervisionRecoveryInfo.EXTRA_SUPERVISION_RECOVERY_INFO
import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING
import android.app.supervision.SupervisionRecoveryInfo.STATE_VERIFIED
import android.content.ComponentName
@@ -27,14 +29,13 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Build
import android.os.PersistableBundle
import android.os.UserHandle
import android.os.UserManager
import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import com.android.settings.supervision.SupervisionPinRecoveryActivity.Companion.EXTRA_RECOVERY_EMAIL
import com.android.settings.supervision.SupervisionPinRecoveryActivity.Companion.EXTRA_RECOVERY_ID
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -63,19 +64,16 @@ class SupervisionPinRecoveryActivityTest {
    private lateinit var shadowPackageManager: ShadowPackageManager
    private val mockSupervisionManager = mock<SupervisionManager>()
    private val mockUserManager = mock<UserManager>()
    private val callingPackage = "com.android.settings"
    private val recoveryEmail = "test@example.com"
    private val recoveryId = "testId"

    @Before
    fun setUp() {
        ShadowRoleManager.addRoleHolder(ROLE_SYSTEM_SUPERVISION, callingPackage, context.user)
        ShadowRoleManager.addRoleHolder(ROLE_SYSTEM_SUPERVISION, CALLING_PACKAGE, context.user)
        shadowPackageManager = shadowOf(context.packageManager)

        // Intent filter for ConfirmSupervisionCredentialsActivity.
        val confirmPinIntentFilter = IntentFilter(ACTION_CONFIRM_PIN)
        val confirmPinComponentName =
            ComponentName(callingPackage, ConfirmSupervisionCredentialsActivity::class.java.name)
            ComponentName(CALLING_PACKAGE, ConfirmSupervisionCredentialsActivity::class.java.name)
        shadowPackageManager.addActivityIfNotPresent(confirmPinComponentName)
        shadowPackageManager.addIntentFilterForActivity(
            confirmPinComponentName,
@@ -83,7 +81,7 @@ class SupervisionPinRecoveryActivityTest {
        )

        // Intent filters for PinRecoveryActivity actions.
        val recoveryComponentName = ComponentName(callingPackage, "SupervisionRecoveryActivity")
        val recoveryComponentName = ComponentName(CALLING_PACKAGE, "SupervisionRecoveryActivity")
        val recoveryIntentFilter =
            IntentFilter(ACTION_SETUP_PIN_RECOVERY).apply {
                addAction(ACTION_UPDATE_PIN_RECOVERY)
@@ -251,8 +249,10 @@ class SupervisionPinRecoveryActivityTest {
                // Now, simulates successful verification after setting verified
                val resultIntent =
                    Intent().apply {
                        putExtra(EXTRA_RECOVERY_EMAIL, recoveryEmail)
                        putExtra(EXTRA_RECOVERY_ID, recoveryId)
                        putExtra(
                            EXTRA_SUPERVISION_RECOVERY_INFO,
                            EXPECTED_SUPERVISION_RECOVERY_INFO,
                        )
                    }
                val testActivityResult = Activity.RESULT_OK

@@ -260,13 +260,7 @@ class SupervisionPinRecoveryActivityTest {
                shadowActivity.receiveResult(setVerifiedIntent, testActivityResult, resultIntent)

                // Verifies that supervisionRecoveryInfo is set correctly.
                verify(mockSupervisionManager).supervisionRecoveryInfo = argThat { info ->
                    info != null &&
                        info.accountName == recoveryEmail &&
                        info.accountType == "default" &&
                        info.state == STATE_VERIFIED &&
                        info.accountData.getString("id") == recoveryId
                }
                verifySupervisionRecoveryInfo(EXPECTED_SUPERVISION_RECOVERY_INFO)
                assertEquals(testActivityResult, shadowActivity.resultCode)
                assertThat(activity.isFinishing).isTrue()
            }
@@ -360,8 +354,10 @@ class SupervisionPinRecoveryActivityTest {
                // Then, simulates successful verification after update
                val resultIntent =
                    Intent().apply {
                        putExtra(EXTRA_RECOVERY_EMAIL, recoveryEmail)
                        putExtra(EXTRA_RECOVERY_ID, recoveryId)
                        putExtra(
                            EXTRA_SUPERVISION_RECOVERY_INFO,
                            EXPECTED_SUPERVISION_RECOVERY_INFO,
                        )
                    }
                val testActivityResult = Activity.RESULT_OK

@@ -369,13 +365,7 @@ class SupervisionPinRecoveryActivityTest {
                shadowActivity.receiveResult(updatePinIntent, testActivityResult, resultIntent)

                // Verifies that supervisionRecoveryInfo is set correctly.
                verify(mockSupervisionManager).supervisionRecoveryInfo = argThat { info ->
                    info != null &&
                        info.accountName == recoveryEmail &&
                        info.accountType == "default" &&
                        info.state == STATE_VERIFIED &&
                        info.accountData.getString("id") == recoveryId
                }
                verifySupervisionRecoveryInfo(EXPECTED_SUPERVISION_RECOVERY_INFO)

                assertEquals(testActivityResult, shadowActivity.resultCode)
                assertThat(activity.isFinishing).isTrue()
@@ -469,8 +459,10 @@ class SupervisionPinRecoveryActivityTest {
                // Then, simulates successful verification after post setup verify
                val resultIntent =
                    Intent().apply {
                        putExtra(EXTRA_RECOVERY_EMAIL, recoveryEmail)
                        putExtra(EXTRA_RECOVERY_ID, recoveryId)
                        putExtra(
                            EXTRA_SUPERVISION_RECOVERY_INFO,
                            EXPECTED_SUPERVISION_RECOVERY_INFO,
                        )
                    }

                val postSetupVerifyIntent = shadowActivity.nextStartedActivity
@@ -481,13 +473,7 @@ class SupervisionPinRecoveryActivityTest {
                )

                // Verifies that supervisionRecoveryInfo is set correctly.
                verify(mockSupervisionManager).supervisionRecoveryInfo = argThat { info ->
                    info != null &&
                        info.accountName == recoveryEmail &&
                        info.accountType == "default" &&
                        info.state == STATE_VERIFIED &&
                        info.accountData.getString("id") == recoveryId
                }
                verifySupervisionRecoveryInfo(EXPECTED_SUPERVISION_RECOVERY_INFO)
                assertThat(shadowActivity.resultCode).isEqualTo(Activity.RESULT_OK)
                assertThat(activity.isFinishing).isTrue()
            }
@@ -631,20 +617,20 @@ class SupervisionPinRecoveryActivityTest {
        ActivityScenario.launch<SupervisionPinRecoveryActivity>(intent).use { scenario ->
            scenario.onActivity { activity ->
                val shadowActivity = shadowOf(activity)
                val resultIntent = Intent().apply { putExtra(EXTRA_RECOVERY_EMAIL, recoveryEmail) }
                val resultIntent =
                    Intent().apply {
                        putExtra(
                            EXTRA_SUPERVISION_RECOVERY_INFO,
                            EXPECTED_PENDING_SUPERVISION_RECOVERY_INFO,
                        )
                    }
                val testActivityResult = Activity.RESULT_OK

                val setupPinIntent = shadowActivity.nextStartedActivity
                shadowActivity.receiveResult(setupPinIntent, testActivityResult, resultIntent)

                // Verifies that supervisionRecoveryInfo is set correctly with PENDING state.
                verify(mockSupervisionManager).supervisionRecoveryInfo = argThat { info ->
                    info != null &&
                        info.accountName == recoveryEmail &&
                        info.accountType == "default" &&
                        info.state == STATE_PENDING &&
                        info.accountData.getString("id") == null
                }
                verifySupervisionRecoveryInfo(EXPECTED_PENDING_SUPERVISION_RECOVERY_INFO)
                assertEquals(testActivityResult, shadowActivity.resultCode)
                assertThat(activity.isFinishing).isTrue()
            }
@@ -672,6 +658,44 @@ class SupervisionPinRecoveryActivityTest {
        }
    }

    @Test
    fun onVerification_setupAction_invalidResult_finishesWithCanceled() {
        // Test scenario where SETUP setup action is finished with invalid data during verification.
        val intent =
            Intent(context, SupervisionPinRecoveryActivity::class.java).apply {
                action = SupervisionPinRecoveryActivity.ACTION_SETUP
            }
        ActivityScenario.launch<SupervisionPinRecoveryActivity>(intent).use { scenario ->
            scenario.onActivity { activity ->
                val shadowActivity = shadowOf(activity)
                val resultIntent =
                    Intent().apply { putExtra(EXTRA_SUPERVISION_RECOVERY_INFO, "dummy") }
                val setupPinIntent = shadowActivity.nextStartedActivity
                shadowActivity.receiveResult(setupPinIntent, Activity.RESULT_OK, resultIntent)

                // Verifies that the activity finishes with result CANCELED.
                verify(mockSupervisionManager, never()).setSupervisionRecoveryInfo(any())
                assertEquals(Activity.RESULT_CANCELED, shadowActivity.resultCode)
                assertThat(activity.isFinishing).isTrue()
            }
        }
    }

    /**
     * Helper function to verify that the SupervisionRecoveryInfo is set correctly.
     *
     * @param expectedInfo The expected SupervisionRecoveryInfo.
     */
    private fun verifySupervisionRecoveryInfo(expectedInfo: SupervisionRecoveryInfo) {
        verify(mockSupervisionManager).supervisionRecoveryInfo = argThat { info ->
            info != null &&
                info.accountName == expectedInfo.accountName &&
                info.accountType == expectedInfo.accountType &&
                info.state == expectedInfo.state &&
                info.accountData.getString("id") == expectedInfo.accountData.getString("id")
        }
    }

    companion object {
        // Intent actions used by SupervisionPinRecoveryActivity and related activities.
        const val ACTION_CONFIRM_PIN =
@@ -688,6 +712,7 @@ class SupervisionPinRecoveryActivityTest {

        // Constants for a supervising user.
        const val SUPERVISING_USER_ID = 5
        const val CALLING_PACKAGE = "com.android.settings"
        val SUPERVISING_USER_INFO =
            UserInfo(
                SUPERVISING_USER_ID,
@@ -696,5 +721,30 @@ class SupervisionPinRecoveryActivityTest {
                /* flags */ 0,
                USER_TYPE_PROFILE_SUPERVISING,
            )

        // Common expected SupervisionRecoveryInfo for verification
        val EXPECTED_SUPERVISION_RECOVERY_INFO =
            SupervisionRecoveryInfo(
                /* accountName */
                "test@example.com",
                /* accountType */
                "default",
                /* state */
                STATE_VERIFIED,
                /* accountData */
                PersistableBundle().apply { putString("id", "testId") },
            )

        val EXPECTED_PENDING_SUPERVISION_RECOVERY_INFO =
            SupervisionRecoveryInfo(
                /* accountName */
                "test@example.com",
                /* accountType */
                "default",
                /* state */
                STATE_PENDING,
                /* accountData */
                null,
            )
    }
}