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

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

Support post setup recovery verify preference in Pin management dashboard

Test: atest SupervisionSetupRecoveryPreferenceTest.kt
Bug: 393657542
Flag: android.app.supervision.flags.enable_supervision_pin_recovery_screen
Change-Id: I9e8af91649367330076a52253235f1c9412019a1
parent f72679fe
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -14494,6 +14494,8 @@ Data usage charges may apply.</string>
    <string name="supervision_delete_pin_preference_summary">This will reset all your supervision settings</string>
    <!-- Title for adding supervision PIN recovery setting entry [CHAR LIMIT=NONE] -->
    <string name="supervision_add_pin_recovery_title">Tap to add recovery</string>
    <!-- Title for adding supervision PIN recovery setting entry [CHAR LIMIT=NONE] -->
    <string name="supervision_verify_pin_recovery_title">Tap to verify recovery</string>
    <!-- Title for web content filters entry [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_title">Web content filters</string>
    <!-- Summary for web content filters entry both on case [CHAR LIMIT=60] -->
+1 −1
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ class SupervisionPinManagementScreen :

    override fun getPreferenceHierarchy(context: Context) =
        preferenceHierarchy(context, this) {
            +SupervisionAddRecoveryPreference()
            +SupervisionSetupRecoveryPreference()
            +TitlelessPreferenceGroup(GROUP_KEY) += {
                +SupervisionPinRecoveryPreference()
                // TODO(b/391992481) implement the screen.
+9 −1
Original line number Diff line number Diff line
@@ -140,7 +140,15 @@ class SupervisionPinRecoveryActivity : FragmentActivity() {
                            SupervisionIntentProvider.PinRecoveryAction.POST_SETUP_VERIFY,
                        )
                    if (postSetupVerifyIntent != null) {
                        val supervisionManager =
                            applicationContext.getSystemService(SupervisionManager::class.java)
                        val recoveryInfo = supervisionManager?.getSupervisionRecoveryInfo()
                        postSetupVerifyIntent.apply {
                            // TODO(b/409805806): will expose the parcelable as system API and pass
                            // it instead.
                            recoveryInfo?.email?.let { putExtra(EXTRA_RECOVERY_EMAIL, it) }
                            verificationLauncher.launch(postSetupVerifyIntent)
                        }
                    } else {
                        handleError("No activity found for post setup PIN recovery verify.")
                    }
+133 −0
Original line number Diff line number Diff line
@@ -21,31 +21,49 @@ import android.app.supervision.SupervisionManager
import android.app.supervision.flags.Flags
import android.content.Context
import android.content.Intent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.supervision.SupervisionUpdateRecoveryEmailPreference.Companion.asMaskedEmail
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.preference.PreferenceBinding

/**
 * Setting on PIN Management screen (Settings > Supervision > Manage Pin) that invokes the flow to
 * add the device PIN recovery method.
 * add the device PIN recovery method or verify an unverified PIN recovery method.
 */
class SupervisionAddRecoveryPreference :
class SupervisionSetupRecoveryPreference :
    PreferenceMetadata,
    PreferenceAvailabilityProvider,
    PreferenceLifecycleProvider,
    PreferenceBinding,
    PreferenceSummaryProvider,
    PreferenceTitleProvider,
    Preference.OnPreferenceClickListener {

    private lateinit var lifeCycleContext: PreferenceLifecycleContext
    private lateinit var setUpRecoveryLauncher: ActivityResultLauncher<Intent>
    override val key: String
        get() = KEY

    override val title: Int
        get() = R.string.supervision_add_pin_recovery_title
    override fun getTitle(context: Context): CharSequence {
        return if (hasEmailToVerify(context)) {
            context.getString(R.string.supervision_verify_pin_recovery_title)
        } else {
            context.getString(R.string.supervision_add_pin_recovery_title)
        }
    }

    override fun getSummary(context: Context): CharSequence? {
        return emailToVerify(context)?.asMaskedEmail()
    }

    // TODO(b/409837094): get icon with dynamic color.
    override val icon: Int
@@ -58,11 +76,17 @@ class SupervisionAddRecoveryPreference :
        return context
            .getSystemService(SupervisionManager::class.java)
            ?.getSupervisionRecoveryInfo()
            ?.let { it.email.isNullOrEmpty() && it.id.isNullOrEmpty() } ?: true
            ?.id
            ?.isEmpty() ?: true
    }

    override fun onCreate(context: PreferenceLifecycleContext) {
        lifeCycleContext = context
        setUpRecoveryLauncher =
            context.registerForActivityResult(
                ActivityResultContracts.StartActivityForResult(),
                ::updateRecoveryInfo,
            )
    }

    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
@@ -70,33 +94,40 @@ class SupervisionAddRecoveryPreference :
        preference.onPreferenceClickListener = this
    }

    override fun onActivityResult(
        context: PreferenceLifecycleContext,
        requestCode: Int,
        resultCode: Int,
        data: Intent?,
    ): Boolean {
        if (requestCode != ADD_RECOVERY_REQUEST_CODE) {
            return false
    fun updateRecoveryInfo(result: ActivityResult) {
        if (result.resultCode == Activity.RESULT_OK) {
            lifeCycleContext.apply {
                notifyPreferenceChange(KEY)
                notifyPreferenceChange(SupervisionPinRecoveryPreference.KEY)
                notifyPreferenceChange(SupervisionUpdateRecoveryEmailPreference.KEY)
                notifyPreferenceChange(SupervisionPinManagementScreen.KEY)
            }
        if (resultCode == Activity.RESULT_OK) {
            context.notifyPreferenceChange(KEY)
            context.notifyPreferenceChange(SupervisionUpdateRecoveryEmailPreference.KEY)
            context.notifyPreferenceChange(SupervisionPinManagementScreen.KEY)
        }
        return true
    }

    override fun onPreferenceClick(preference: Preference): Boolean {
        val intent =
            Intent(lifeCycleContext, SupervisionPinRecoveryActivity::class.java)
                .setAction(SupervisionPinRecoveryActivity.ACTION_SETUP_VERIFIED)
        lifeCycleContext.startActivityForResult(intent, ADD_RECOVERY_REQUEST_CODE, null)
        val intent = Intent(lifeCycleContext, SupervisionPinRecoveryActivity::class.java)
        if (hasEmailToVerify(lifeCycleContext)) {
            intent.action = SupervisionPinRecoveryActivity.ACTION_POST_SETUP_VERIFY
        } else {
            intent.action = SupervisionPinRecoveryActivity.ACTION_SETUP_VERIFIED
        }
        setUpRecoveryLauncher.launch(intent)
        return true
    }

    private fun emailToVerify(context: Context): String? {
        return context
            .getSystemService(SupervisionManager::class.java)
            ?.supervisionRecoveryInfo
            ?.email
    }

    private fun hasEmailToVerify(context: Context): Boolean {
        return !emailToVerify(context).isNullOrEmpty()
    }

    companion object {
        const val KEY = "supervision_add_recovery"
        const val ADD_RECOVERY_REQUEST_CODE = 1
        const val KEY = "supervision_setup_recovery"
    }
}
+84 −19
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.content.Intent
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,23 +37,25 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@RunWith(AndroidJUnit4::class)
class SupervisionAddRecoveryPreferenceTest {
class SupervisionSetupRecoveryPreferenceTest {

    private val appContext: Context = ApplicationProvider.getApplicationContext()
    private val mockLifeCycleContext = mock<PreferenceLifecycleContext>()
    private val mockSupervisionManager = mock<SupervisionManager>()

    private val mockActivityResultLauncher = mock<ActivityResultLauncher<Intent>>()

    @get:Rule val setFlagsRule = SetFlagsRule()
    private var preference = SupervisionAddRecoveryPreference()
    private var preference = SupervisionSetupRecoveryPreference()
    private val context =
        object : ContextWrapper(appContext) {
            override fun getSystemService(name: String): Any =
@@ -63,12 +67,46 @@ class SupervisionAddRecoveryPreferenceTest {

    @Before
    fun setUp() {
        whenever(
                mockLifeCycleContext.registerForActivityResult(
                    any<ActivityResultContracts.StartActivityForResult>(),
                    any(),
                )
            )
            .thenReturn(mockActivityResultLauncher)
        preference.onCreate(mockLifeCycleContext)
    }

    @Test
    fun getTitle() {
        assertThat(preference.title).isEqualTo(R.string.supervision_add_pin_recovery_title)
    fun getTitle_addRecovery() {
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(null)

        assertThat(preference.getTitle(context))
            .isEqualTo(context.getString(R.string.supervision_add_pin_recovery_title))
    }

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

        assertThat(preference.getTitle(context))
            .isEqualTo(context.getString(R.string.supervision_verify_pin_recovery_title))
    }

    @Test
    fun getSummary_addRecovery() {
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(null)

        assertThat(preference.getSummary(context)).isNull()
    }

    @Test
    fun getSummary_verifyRecovery() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "test@gmail.com" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

        assertThat(preference.getSummary(context)).isEqualTo("t••t@gmail.com")
    }

    @Test
@@ -90,10 +128,23 @@ class SupervisionAddRecoveryPreferenceTest {

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_PIN_RECOVERY_SCREEN)
    fun flagEnabled_recoveryExist_notAvailable() {
    fun flagEnabled_recoveryEmailExist_isAvailable() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "email" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

        assertThat(preference.isAvailable(context)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_PIN_RECOVERY_SCREEN)
    fun flagEnabled_recoveryIdExist_NotAvailable() {
        val recoveryInfo =
            SupervisionRecoveryInfo().apply {
                email = "email"
                id = "id"
            }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

        assertThat(preference.isAvailable(context)).isFalse()
    }

@@ -106,31 +157,45 @@ class SupervisionAddRecoveryPreferenceTest {
    }

    @Test
    fun onClick_triggersPinRecoveryActivity() {
    fun addRecovery_onClick_triggersPinRecoveryActivity() {
        whenever(mockSupervisionManager.supervisionRecoveryInfo)
            .thenReturn(SupervisionRecoveryInfo())
        val widget: Preference = preference.createAndBindWidget(context)

        mockLifeCycleContext.stub {
            on { findPreference<Preference>(SupervisionUpdateRecoveryEmailPreference.KEY) } doReturn
            on { findPreference<Preference>(SupervisionSetupRecoveryPreference.KEY) } doReturn
                widget
            on { getSystemService(SupervisionManager::class.java) } doReturn mockSupervisionManager
        }
        widget.performClick()

        verifyPinRecoveryActivityStarted(SupervisionPinRecoveryActivity.ACTION_SETUP_VERIFIED)
    }

    @Test
    fun verifyRecovery_onClick_triggersPinRecoveryActivity() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "test@gmail.com" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)
        val widget: Preference = preference.createAndBindWidget(context)

        mockLifeCycleContext.stub {
            on { findPreference<Preference>(SupervisionSetupRecoveryPreference.KEY) } doReturn
                widget
            on { getSystemService(SupervisionManager::class.java) } doReturn mockSupervisionManager
        }

        widget.performClick()

        verifyPinRecoveryActivityStarted()
        verifyPinRecoveryActivityStarted(SupervisionPinRecoveryActivity.ACTION_POST_SETUP_VERIFY)
    }

    private fun verifyPinRecoveryActivityStarted() {
    private fun verifyPinRecoveryActivityStarted(expectedAction: String) {
        val intentCaptor = argumentCaptor<Intent>()
        verify(mockLifeCycleContext)
            .startActivityForResult(
                intentCaptor.capture(),
                eq(SupervisionAddRecoveryPreference.ADD_RECOVERY_REQUEST_CODE),
                eq(null),
            )
        verify(mockActivityResultLauncher).launch(intentCaptor.capture())
        assertThat(intentCaptor.allValues.size).isEqualTo(1)
        assertThat(intentCaptor.firstValue.component?.className)
        val intent = intentCaptor.firstValue
        assertThat(intent.component?.className)
            .isEqualTo(SupervisionPinRecoveryActivity::class.java.name)
        assertThat(intentCaptor.firstValue.action)
            .isEqualTo(SupervisionPinRecoveryActivity.ACTION_SETUP_VERIFIED)
        assertThat(intent.action).isEqualTo(expectedAction)
    }
}
Loading