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

Commit 1acba2a0 authored by Sandy Pan's avatar Sandy Pan Committed by Android (Google) Code Review
Browse files

Merge "Support post setup recovery verify preference in Pin management dashboard" into main

parents 273b726b afc8a4c5
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -14519,6 +14519,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
@@ -82,7 +82,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