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

Commit 0730c541 authored by Xiaomiao Zhang's avatar Xiaomiao Zhang
Browse files

Invoke pin verification upon switching safe search options

Test: atest SupervisionWebContentFiltersScreenTest
Test: atest SupervisionSafeSearchPreferenceTest
Test: deployed locally to a physical device
Flag: android.app.supervision.flags.enable_web_content_filters_screen
Bug: 408490556
Change-Id: Ifce317e919cd596e04b5b9624a39ed09c8a7aafe
parent e4f202d9
Loading
Loading
Loading
Loading
+37 −6
Original line number Diff line number Diff line
@@ -15,11 +15,19 @@
 */
package com.android.settings.supervision

import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import com.android.settings.R
import com.android.settingslib.datastore.Permissions
import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
@@ -30,7 +38,15 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference
/** Base class of web content filters SafeSearch preferences. */
sealed class SupervisionSafeSearchPreference(
    protected val dataStore: SupervisionSafeSearchDataStore
) : BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding {
) :
    BooleanValuePreference,
    SelectorWithWidgetPreference.OnClickListener,
    PreferenceBinding,
    PreferenceLifecycleProvider {
    private lateinit var lifeCycleContext: PreferenceLifecycleContext

    private lateinit var supervisionCredentialLauncher: ActivityResultLauncher<Intent>

    override fun storage(context: Context) = dataStore

    override fun getReadPermissions(context: Context) = Permissions.EMPTY
@@ -52,12 +68,15 @@ sealed class SupervisionSafeSearchPreference(

    override fun createWidget(context: Context) = SelectorWithWidgetPreference(context)

    override fun onRadioButtonClicked(emiter: SelectorWithWidgetPreference) {
        emiter.parent?.forEachRecursively {
            if (it is SelectorWithWidgetPreference) {
                it.isChecked = it == emiter
            }
    override fun onCreate(context: PreferenceLifecycleContext) {
        lifeCycleContext = context
        supervisionCredentialLauncher =
            context.registerForActivityResult(StartActivityForResult(), ::onConfirmCredentials)
    }

    override fun onRadioButtonClicked(emiter: SelectorWithWidgetPreference) {
        val intent = Intent(lifeCycleContext, ConfirmSupervisionCredentialsActivity::class.java)
        supervisionCredentialLauncher.launch(intent)
    }

    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
@@ -67,6 +86,18 @@ sealed class SupervisionSafeSearchPreference(
            it.setOnClickListener(this)
        }
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    fun onConfirmCredentials(result: ActivityResult) {
        if (result.resultCode == Activity.RESULT_OK) {
            val preference = lifeCycleContext.findPreference<SelectorWithWidgetPreference>(key)
            preference?.parent?.forEachRecursively {
                if (it is SelectorWithWidgetPreference) {
                    it.isChecked = it.key == key
                }
            }
        }
    }
}

/** The SafeSearch filter on preference. */
+78 −2
Original line number Diff line number Diff line
@@ -15,13 +15,20 @@
 */
package com.android.settings.supervision

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.provider.Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED
import android.provider.Settings.SettingNotFoundException
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
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.android.settingslib.widget.SelectorWithWidgetPreference
import com.google.common.truth.Truth.assertThat
@@ -29,10 +36,20 @@ import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify

@RunWith(AndroidJUnit4::class)
class SupervisionSafeSearchPreferenceTest {
    private val context: Context = ApplicationProvider.getApplicationContext()

    private lateinit var mockLifeCycleContext: PreferenceLifecycleContext
    private lateinit var mockActivityResultLauncher: ActivityResultLauncher<Intent>
    private lateinit var dataStore: SupervisionSafeSearchDataStore
    private lateinit var searchFilterOffPreference: SupervisionSearchFilterOffPreference
    private lateinit var searchFilterOnPreference: SupervisionSearchFilterOnPreference
@@ -40,8 +57,14 @@ class SupervisionSafeSearchPreferenceTest {
    @Before
    fun setUp() {
        dataStore = SupervisionSafeSearchDataStore(context)
        mockLifeCycleContext = mock(PreferenceLifecycleContext::class.java)
        mockActivityResultLauncher =
            mock(ActivityResultLauncher::class.java) as ActivityResultLauncher<Intent>
        mockConfirmSupervisionCredentialsActivity()
        searchFilterOffPreference = SupervisionSearchFilterOffPreference(dataStore)
        searchFilterOffPreference.onCreate(mockLifeCycleContext)
        searchFilterOnPreference = SupervisionSearchFilterOnPreference(dataStore)
        searchFilterOnPreference.onCreate(mockLifeCycleContext)
    }

    @Test
@@ -84,6 +107,25 @@ class SupervisionSafeSearchPreferenceTest {
        assertThat(getFilterOnWidget().isChecked).isTrue()
    }

    @Test
    fun clickBlockExplicitSites_failedToEnablesFilter_activityFailed() {
        Settings.Secure.putInt(context.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, 0)
        val filterOnWidget = getFilterOnWidget()
        assertThat(filterOnWidget.isChecked).isFalse()

        filterOnWidget.performClick()
        verifyConfirmSupervisionCredentialsActivity()
        searchFilterOnPreference.onConfirmCredentials(
            ActivityResult(Activity.RESULT_CANCELED, null)
        )

        assertThat(
                Settings.Secure.getInt(context.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED)
            )
            .isEqualTo(0)
        assertThat(filterOnWidget.isChecked).isFalse()
    }

    @Test
    fun clickBlockExplicitSites_enablesFilter() {
        Settings.Secure.putInt(context.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, 0)
@@ -91,6 +133,8 @@ class SupervisionSafeSearchPreferenceTest {
        assertThat(filterOnWidget.isChecked).isFalse()

        filterOnWidget.performClick()
        verifyConfirmSupervisionCredentialsActivity()
        searchFilterOnPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))

        assertThat(
                Settings.Secure.getInt(context.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED)
@@ -106,6 +150,8 @@ class SupervisionSafeSearchPreferenceTest {
        assertThat(filterOffWidget.isChecked).isFalse()

        filterOffWidget.performClick()
        verifyConfirmSupervisionCredentialsActivity()
        searchFilterOffPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))

        assertThat(
                Settings.Secure.getInt(context.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED)
@@ -115,10 +161,40 @@ class SupervisionSafeSearchPreferenceTest {
    }

    private fun getFilterOnWidget(): SelectorWithWidgetPreference {
        return searchFilterOnPreference.createAndBindWidget(context)
        val widget: SelectorWithWidgetPreference =
            searchFilterOnPreference.createAndBindWidget(context)
        mockLifeCycleContext.stub {
            on { findPreference<Preference>(SupervisionSearchFilterOnPreference.KEY) } doReturn
                widget
            on { requirePreference<Preference>(SupervisionSearchFilterOnPreference.KEY) } doReturn
                widget
        }
        return widget
    }

    private fun getFilterOffWidget(): SelectorWithWidgetPreference {
        return searchFilterOffPreference.createAndBindWidget(context)
        val widget: SelectorWithWidgetPreference =
            searchFilterOffPreference.createAndBindWidget(context)
        mockLifeCycleContext.stub {
            on { findPreference<Preference>(SupervisionSearchFilterOffPreference.KEY) } doReturn
                widget
            on { requirePreference<Preference>(SupervisionSearchFilterOffPreference.KEY) } doReturn
                widget
        }
        return widget
    }

    private fun mockConfirmSupervisionCredentialsActivity() {
        `when`(mockLifeCycleContext.registerForActivityResult(any<StartActivityForResult>(), any()))
            .thenReturn(mockActivityResultLauncher)
    }

    private fun verifyConfirmSupervisionCredentialsActivity() {
        val intentCaptor = argumentCaptor<Intent>()
        verify(mockActivityResultLauncher).launch(intentCaptor.capture())

        assertThat(intentCaptor.allValues.size).isEqualTo(1)
        assertThat(intentCaptor.firstValue.component?.className)
            .isEqualTo(ConfirmSupervisionCredentialsActivity::class.java.name)
    }
}
+44 −3
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows.shadowOf

@RunWith(AndroidJUnit4::class)
class SupervisionWebContentFiltersScreenTest {
@@ -133,7 +134,39 @@ class SupervisionWebContentFiltersScreenTest {

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
    fun switchSafeSearchPreferences() {
    fun switchSafeSearchPreferences_succeedWithParentPin() {
        FragmentScenario.launchInContainer(supervisionWebContentFiltersScreen.fragmentClass())
            .onFragment { fragment ->
                val searchFilterOffWidget =
                    fragment.findPreference<SelectorWithWidgetPreference>(
                        SupervisionSearchFilterOffPreference.KEY
                    )!!
                val searchFilterOnWidget =
                    fragment.findPreference<SelectorWithWidgetPreference>(
                        SupervisionSearchFilterOnPreference.KEY
                    )!!

                assertThat(searchFilterOffWidget.isChecked).isTrue()
                assertThat(searchFilterOnWidget.isChecked).isFalse()

                searchFilterOnWidget.performClick()

                // Pretend the PIN verification succeeded.
                val activity = shadowOf(fragment.activity)
                activity.receiveResult(
                    activity.nextStartedActivityForResult.intent,
                    Activity.RESULT_OK,
                    null,
                )

                assertThat(searchFilterOnWidget.isChecked).isTrue()
                assertThat(searchFilterOffWidget.isChecked).isFalse()
            }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
    fun switchSafeSearchPreferences_failedWithParentPin() {
        FragmentScenario.launchInContainer(supervisionWebContentFiltersScreen.fragmentClass())
            .onFragment { fragment ->
                val searchFilterOffPreference =
@@ -150,8 +183,16 @@ class SupervisionWebContentFiltersScreenTest {

                searchFilterOnPreference.performClick()

                assertThat(searchFilterOnPreference.isChecked).isTrue()
                assertThat(searchFilterOffPreference.isChecked).isFalse()
                // Pretend the PIN verification failed.
                val activity = shadowOf(fragment.activity)
                activity.receiveResult(
                    activity.nextStartedActivityForResult.intent,
                    Activity.RESULT_CANCELED,
                    null,
                )

                assertThat(searchFilterOnPreference.isChecked).isFalse()
                assertThat(searchFilterOffPreference.isChecked).isTrue()
            }
    }
}