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

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

Merge "Update supervision recovery update preference in Pin management dashboard" into main

parents 506c119d e311bc6d
Loading
Loading
Loading
Loading
+89 −2
Original line number Diff line number Diff line
@@ -15,23 +15,110 @@
 */
package com.android.settings.supervision

import android.app.Activity
import android.app.supervision.SupervisionManager
import android.app.supervision.flags.Flags
import android.content.Context
import android.content.Intent
import androidx.preference.Preference
import com.android.settings.R
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.preference.PreferenceBinding

/**
 * Setting on PIN Management screen (Settings > Supervision > Manage Pin) that invokes the flow to
 * update the PIN recovery email.
 */
class SupervisionUpdateRecoveryEmailPreference : PreferenceMetadata {
class SupervisionUpdateRecoveryEmailPreference :
    PreferenceMetadata,
    PreferenceAvailabilityProvider,
    PreferenceLifecycleProvider,
    PreferenceBinding,
    PreferenceSummaryProvider,
    Preference.OnPreferenceClickListener {

    private lateinit var lifeCycleContext: PreferenceLifecycleContext
    override val key: String
        get() = KEY

    override val title: Int
        get() = R.string.supervision_update_recovery_email_preference_title

    // TODO(b/402987522): Add update recovery email flow in settings.
    override fun getSummary(context: Context): CharSequence? {
        return context
            .getSystemService(SupervisionManager::class.java)
            ?.getSupervisionRecoveryInfo()
            ?.email
            ?.asMaskedEmail()
    }

    override fun isAvailable(context: Context): Boolean {
        if (!Flags.enableSupervisionPinRecoveryScreen()) {
            return false
        }
        return context
            .getSystemService(SupervisionManager::class.java)
            ?.getSupervisionRecoveryInfo()
            ?.let { !it.email.isNullOrEmpty() && !it.id.isNullOrEmpty() } ?: false
    }

    override fun onCreate(context: PreferenceLifecycleContext) {
        lifeCycleContext = context
    }

    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
        super.bind(preference, metadata)
        preference.onPreferenceClickListener = this
    }

    override fun onActivityResult(
        context: PreferenceLifecycleContext,
        requestCode: Int,
        resultCode: Int,
        data: Intent?,
    ): Boolean {
        if (requestCode != REQUEST_CODE_UPDATE_RECOVERY) {
            return false
        }
        if (resultCode == Activity.RESULT_OK) {
            context.notifyPreferenceChange(KEY)
        }
        return true
    }

    override fun onPreferenceClick(preference: Preference): Boolean {
        val intent =
            Intent(lifeCycleContext, SupervisionPinRecoveryActivity::class.java)
                .setAction(SupervisionPinRecoveryActivity.ACTION_UPDATE)
        lifeCycleContext.startActivityForResult(intent, REQUEST_CODE_UPDATE_RECOVERY, null)
        return true
    }

    companion object {
        const val KEY = "supervision_update_recovery_email"
        const val REQUEST_CODE_UPDATE_RECOVERY = 2

        fun String.asMaskedEmail(): String? {
            val atIndex = this.indexOf('@')
            if (atIndex <= 0) {
                return null // Invalid email format
            }
            val username = this.substring(0, atIndex)
            val domain = this.substring(atIndex)

            return if (username.length <= 1) {
                username + domain
            } else if (username.length == 2) {
                "${username.first()}•$domain"
            } else {
                "${username.first()}" +
                    "•".repeat(username.length - 2) +
                    "${username.last()}$domain"
            }
        }
    }
}
+151 −4
Original line number Diff line number Diff line
@@ -15,24 +15,171 @@
 */
package com.android.settings.supervision

import android.app.supervision.SupervisionManager
import android.app.supervision.SupervisionRecoveryInfo
import android.app.supervision.flags.Flags
import android.content.Context
import android.content.ContextWrapper
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.preference.Preference
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.preference.createAndBindWidget
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
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 SupervisionUpdateRecoveryEmailPreferenceTest {
    private val context: Context = ApplicationProvider.getApplicationContext()

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

    @get:Rule val setFlagsRule = SetFlagsRule()
    private val preference = SupervisionUpdateRecoveryEmailPreference()
    private val context =
        object : ContextWrapper(appContext) {
            override fun getSystemService(name: String): Any =
                when (name) {
                    Context.SUPERVISION_SERVICE -> mockSupervisionManager
                    else -> super.getSystemService(name)
                }
        }

    @Before
    fun setUp() {
        preference.onCreate(mockLifeCycleContext)
    }

    @Test
    fun getTitle() {
        assertThat(supervisionUpdateRecoveryEmailPreference.title)
        assertThat(preference.title)
            .isEqualTo(R.string.supervision_update_recovery_email_preference_title)
    }

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

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

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

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

    @Test
    fun getSummary_emailAvailable_singleCharEmail() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "t@example.com" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

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

    @Test
    fun getSummary_emailAvailable_twoCharsEmail() {
        val recoveryInfo = SupervisionRecoveryInfo().apply { email = "te@example.com" }
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(recoveryInfo)

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

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

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

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_PIN_RECOVERY_SCREEN)
    fun flagEnabled_recoveryInfoNotExist_notAvailable() {
        whenever(mockSupervisionManager.supervisionRecoveryInfo).thenReturn(null)

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

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

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

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

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

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

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

    @Test
    fun onClick_triggersPinRecoveryActivity() {
        val widget: Preference = preference.createAndBindWidget(context)

        mockLifeCycleContext.stub {
            on { findPreference<Preference>(SupervisionUpdateRecoveryEmailPreference.KEY) } doReturn
                widget
        }

        widget.performClick()

        verifyPinRecoveryActivityStarted()
    }

    private fun verifyPinRecoveryActivityStarted() {
        val intentCaptor = argumentCaptor<Intent>()
        verify(mockLifeCycleContext)
            .startActivityForResult(
                intentCaptor.capture(),
                eq(SupervisionUpdateRecoveryEmailPreference.REQUEST_CODE_UPDATE_RECOVERY),
                eq(null),
            )
        assertThat(intentCaptor.allValues.size).isEqualTo(1)
        assertThat(intentCaptor.firstValue.component?.className)
            .isEqualTo(SupervisionPinRecoveryActivity::class.java.name)
        assertThat(intentCaptor.firstValue.action)
            .isEqualTo(SupervisionPinRecoveryActivity.ACTION_UPDATE)
    }
}