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

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

Update supervision recovery update preference in Pin management dashboard

Test: atest SupervisionUpdateRecoveryEmailPreferenceTest.kt
Bug: 393657542
Flag: android.app.supervision.flags.enable_supervision_pin_recovery_screen
Change-Id: I21936560c0f171f1a637d7779da8f71673ddbe50
parent a91dee64
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)
    }
}