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

Commit 6228f688 authored by Xiaomiao Zhang's avatar Xiaomiao Zhang
Browse files

Convert supervision safe sites radio button group to a switch.

Test: atest SupervisionSafeSitesSwitchPreferenceTest
Test: atest SupervisionWebContentFiltersScreenTest
Test: deploy locally
Flag: android.app.supervision.flags.enable_web_content_filters_screen
Fix: 417804977
Change-Id: I1e8fc83876abd29c6d2519a6e2fcc32b7d68fa2e
parent b4998320
Loading
Loading
Loading
Loading
+2 −6
Original line number Diff line number Diff line
@@ -14489,12 +14489,8 @@ Data usage charges may apply.</string>
    <string name="supervision_web_content_filters_title">Web content filters</string>
    <!-- Title for web content filters browser category [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_browser_title">Websites</string>
    <!-- Title for web content filters browser category block explicit sites option [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_browser_block_explicit_sites_title">Block explicit web content</string>
    <!-- Summary for web content filters browser category block explicit sites option [CHAR LIMIT=NONE] -->
    <string name="supervision_web_content_filters_browser_block_explicit_sites_summary">For browsers on this device</string>
    <!-- Title for web content filters browser category allow all sites option [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_browser_allow_all_sites_title">Allow all sites</string>
    <!-- Title for web content filters browser filters [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_browser_filter_title">Block explicit sites</string>
    <!-- Title for web content filters search category [CHAR LIMIT=60] -->
    <string name="supervision_web_content_filters_search_title">Search results</string>
    <!-- Title for web content filters search category filter on option [CHAR LIMIT=60] -->
+4 −19
Original line number Diff line number Diff line
@@ -34,28 +34,14 @@ class SupervisionSafeSitesDataStore(
    override fun contains(key: String) = settingsStore.contains(BROWSER_CONTENT_FILTERS_ENABLED)

    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
        val settingValue = settingsStore.getInt(BROWSER_CONTENT_FILTERS_ENABLED)
        val isFilterOff: Boolean = settingValue == null || settingValue <= 0
        return when (key) {
            SupervisionAllowAllSitesPreference.KEY -> isFilterOff

            SupervisionBlockExplicitSitesPreference.KEY -> !isFilterOff

            else -> null
        }
            as T?
        val settingValue: Int? = settingsStore.getInt(BROWSER_CONTENT_FILTERS_ENABLED)
        return (settingValue != null && (settingValue > 0)) as T?
    }

    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
        if (value !is Boolean) return
        when (key) {
            SupervisionAllowAllSitesPreference.KEY ->
                settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, !value)

            SupervisionBlockExplicitSitesPreference.KEY ->
        settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, value)
    }
    }

    override fun onFirstObserverAdded() {
        // observe the underlying storage key
@@ -64,8 +50,7 @@ class SupervisionSafeSitesDataStore(

    override fun onKeyChanged(key: String, reason: Int) {
        // forward data change to preference hierarchy key
        notifyChange(SupervisionBlockExplicitSitesPreference.KEY, reason)
        notifyChange(SupervisionAllowAllSitesPreference.KEY, reason)
        notifyChange(SupervisionSafeSitesSwitchPreference.KEY, reason)
    }

    override fun onLastObserverRemoved() {
+30 −50
Original line number Diff line number Diff line
@@ -25,31 +25,31 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.android.settings.R
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settings.overlay.FeatureFactory
import com.android.settingslib.datastore.SettingsSecureStore
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.preference.BooleanValuePreferenceBinding
import com.android.settingslib.metadata.SwitchPreference
import com.android.settingslib.preference.SwitchPreferenceBinding
import com.android.settingslib.supervision.SupervisionIntentProvider
import com.android.settingslib.widget.SelectorWithWidgetPreference

/** Base class of web content filters Safe sites preferences. */
sealed class SupervisionSafeSitesPreference(
    protected val dataStore: SupervisionSafeSitesDataStore
) :
    BooleanValuePreference,
    BooleanValuePreferenceBinding,
    PreferenceActionMetricsProvider,
    SelectorWithWidgetPreference.OnClickListener,

/** Web content filters browser filter preference. */
class SupervisionSafeSitesSwitchPreference(protected val dataStore: SupervisionSafeSitesDataStore) :
    SwitchPreference(KEY),
    SwitchPreferenceBinding,
    Preference.OnPreferenceChangeListener,
    PreferenceLifecycleProvider {
    private lateinit var lifeCycleContext: PreferenceLifecycleContext

    private lateinit var supervisionCredentialLauncher: ActivityResultLauncher<Intent>

    override val title
        get() = R.string.supervision_web_content_filters_browser_filter_title

    override fun storage(context: Context) = dataStore

    override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()
@@ -62,68 +62,48 @@ sealed class SupervisionSafeSitesPreference(
    override fun getWritePermit(context: Context, callingPid: Int, callingUid: Int) =
        ReadWritePermit.DISALLOW

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

    override fun onCreate(context: PreferenceLifecycleContext) {
        lifeCycleContext = context
        supervisionCredentialLauncher =
            context.registerForActivityResult(StartActivityForResult(), ::onConfirmCredentials)
    }

    override fun onRadioButtonClicked(emitter: SelectorWithWidgetPreference) {
    override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
        if (newValue !is Boolean) return true

        val intent =
            SupervisionIntentProvider.getConfirmSupervisionCredentialsIntent(lifeCycleContext)
        if (intent != null) {
            supervisionCredentialLauncher.launch(intent)
        }
        return false
    }

    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
        super.bind(preference, metadata)
        (preference as SelectorWithWidgetPreference).setOnClickListener(this)
        preference.onPreferenceChangeListener = this
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    fun onConfirmCredentials(result: ActivityResult) {
        if (result.resultCode == Activity.RESULT_OK) {
            lifeCycleContext.requirePreference<SelectorWithWidgetPreference>(key).isChecked = true
            val preference = lifeCycleContext.requirePreference<SwitchPreferenceCompat>(key)
            val isChecked = preference.isChecked
            preference.isChecked = !isChecked
            logMetrics(preference)
        }
    }
}

/** The "Try to block explicit sites" preference. */
class SupervisionBlockExplicitSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
    SupervisionSafeSitesPreference(dataStore) {
    override val key
        get() = KEY

    override val title
        get() = R.string.supervision_web_content_filters_browser_block_explicit_sites_title

    override val summary
        get() = R.string.supervision_web_content_filters_browser_block_explicit_sites_summary

    override val preferenceActionMetrics: Int
        get() = ACTION_SUPERVISION_BLOCK_EXPLICIT_SITES

    companion object {
        const val KEY = "web_content_filters_browser_block_explicit_sites"
    }
    private fun logMetrics(preference: SwitchPreferenceCompat) {
        val isChecked = preference.isChecked
        val metricsFeatureProvider = FeatureFactory.featureFactory.metricsFeatureProvider
        val action =
            if (isChecked) ACTION_SUPERVISION_BLOCK_EXPLICIT_SITES
            else ACTION_SUPERVISION_ALLOW_ALL_SITES
        metricsFeatureProvider.action(preference.context, action)
    }

/** The "Allow all sites" preference. */
class SupervisionAllowAllSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
    SupervisionSafeSitesPreference(dataStore) {
    override val key
        get() = KEY

    override val title
        get() = R.string.supervision_web_content_filters_browser_allow_all_sites_title

    override val preferenceActionMetrics: Int
        get() = ACTION_SUPERVISION_ALLOW_ALL_SITES

    companion object {
        const val KEY = "web_content_filters_browser_allow_all_sites"
        const val KEY = "web_content_filters_browser_filter"
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -71,8 +71,7 @@ open class SupervisionWebContentFiltersScreen : PreferenceScreenMixin, Preferenc
            ) +=
                {
                    val dataStore = SupervisionSafeSitesDataStore(context)
                    +SupervisionBlockExplicitSitesPreference(dataStore)
                    +SupervisionAllowAllSitesPreference(dataStore)
                    +SupervisionSafeSitesSwitchPreference(dataStore)
                }
            +PreferenceCategory(
                SEARCH_RADIO_BUTTON_GROUP,
+40 −56
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
package com.android.settings.supervision

import android.app.Activity
import android.app.settings.SettingsEnums.ACTION_SUPERVISION_ALLOW_ALL_SITES
import android.app.settings.SettingsEnums.ACTION_SUPERVISION_BLOCK_EXPLICIT_SITES
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -25,6 +27,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
@@ -32,7 +35,6 @@ import com.android.settings.testutils.MetricsRule
import com.android.settingslib.datastore.SettingsSecureStore
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
import org.junit.Before
import org.junit.Rule
@@ -49,13 +51,12 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions

@RunWith(AndroidJUnit4::class)
class SupervisionSafeSitesPreferenceTest {
class SupervisionSafeSitesSwitchPreferenceTest {
    @get:Rule val metricsRule = MetricsRule()

    private val context: Context = ApplicationProvider.getApplicationContext()
    private val dataStore = SupervisionSafeSitesDataStore(context)
    private val allowAllSitesPreference = SupervisionAllowAllSitesPreference(dataStore)
    private val blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference(dataStore)
    private val switchPreference = SupervisionSafeSitesSwitchPreference(dataStore)

    private val mockActivityResultLauncher: ActivityResultLauncher<Intent> = mock()
    private val mockPackageManager: PackageManager = mock {
@@ -70,109 +71,92 @@ class SupervisionSafeSitesPreferenceTest {

    @Before
    fun setUp() {
        allowAllSitesPreference.onCreate(mockLifeCycleContext)
        blockExplicitSitesPreference.onCreate(mockLifeCycleContext)
        switchPreference.onCreate(mockLifeCycleContext)
    }

    @Test
    fun getTitle_allowAllSites() {
        assertThat(allowAllSitesPreference.title)
            .isEqualTo(R.string.supervision_web_content_filters_browser_allow_all_sites_title)
    }

    @Test
    fun getTitle_blockExplicitSites() {
        assertThat(blockExplicitSitesPreference.title)
            .isEqualTo(R.string.supervision_web_content_filters_browser_block_explicit_sites_title)
    }

    @Test
    fun getSummary_blockExplicitSites() {
        assertThat(blockExplicitSitesPreference.summary)
            .isEqualTo(
                R.string.supervision_web_content_filters_browser_block_explicit_sites_summary
            )
    fun getTitle() {
        assertThat(switchPreference.title)
            .isEqualTo(R.string.supervision_web_content_filters_browser_filter_title)
    }

    @Test
    fun allowAllSitesIsChecked_whenNoValueIsSet() {
        setDataStoreValue(null)
        assertThat(blockExplicitSitesPreference.createWidget().isChecked).isFalse()
        assertThat(allowAllSitesPreference.createWidget().isChecked).isTrue()
        assertThat(switchPreference.createWidget().isChecked).isFalse()
    }

    @Test
    fun blockExplicitSitesIsChecked_whenPreviouslyEnabled() {
        setDataStoreValue(1)
        assertThat(allowAllSitesPreference.createWidget().isChecked).isFalse()
        assertThat(blockExplicitSitesPreference.createWidget().isChecked).isTrue()
        assertThat(switchPreference.createWidget().isChecked).isTrue()
    }

    @Test
    fun clickBlockExplicitSites_credentialFailed() {
    fun blockExplicitSites_credentialFailed() {
        setDataStoreValue(0)
        val blockExplicitSitesWidget = blockExplicitSitesPreference.createWidget()
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()
        val filterWidget = switchPreference.createWidget()
        assertThat(filterWidget.isChecked).isFalse()

        blockExplicitSitesWidget.performClick()
        filterWidget.performClick()

        verifyConfirmSupervisionCredentialsActivity()
        blockExplicitSitesPreference.onConfirmCredentials(
            ActivityResult(Activity.RESULT_CANCELED, null)
        )
        switchPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_CANCELED, null))

        verifyNoInteractions(metricsRule.metricsFeatureProvider)
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()
        assertThat(filterWidget.isChecked).isFalse()
        assertThat(getDataStoreValue()).isFalse()
    }

    @Test
    fun clickBlockExplicitSites_unresolvedIntent_activityNotLaunched() {
    fun blockExplicitSites_unresolvedIntent_activityNotLaunched() {
        mockPackageManager.stub {
            on { queryIntentActivitiesAsUser(any<Intent>(), anyInt(), anyInt()) } doReturn listOf()
        }

        setDataStoreValue(0)
        val blockExplicitSitesWidget = blockExplicitSitesPreference.createWidget()
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()
        val filterWidget = switchPreference.createWidget()
        assertThat(filterWidget.isChecked).isFalse()

        blockExplicitSitesWidget.performClick()
        filterWidget.performClick()

        verify(mockActivityResultLauncher, never()).launch(any())
        verifyNoInteractions(metricsRule.metricsFeatureProvider)
        assertThat(getDataStoreValue()).isFalse()
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()
        assertThat(filterWidget.isChecked).isFalse()
    }

    @Test
    fun clickBlockExplicitSites_enablesFilter() {
    fun blockExplicitSites_enablesFilter() {
        setDataStoreValue(-1)
        val blockExplicitSitesWidget = blockExplicitSitesPreference.createWidget()
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()
        val filterWidget = switchPreference.createWidget()
        assertThat(filterWidget.isChecked).isFalse()

        blockExplicitSitesWidget.performClick()
        filterWidget.performClick()

        verifyConfirmSupervisionCredentialsActivity()
        blockExplicitSitesPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))
        switchPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))

        assertThat(blockExplicitSitesWidget.isChecked).isTrue()
        assertThat(filterWidget.isChecked).isTrue()
        assertThat(getDataStoreValue()).isTrue()
        verify(metricsRule.metricsFeatureProvider).changed(0, blockExplicitSitesPreference.key, 1)
        verify(metricsRule.metricsFeatureProvider)
            .action(context, ACTION_SUPERVISION_BLOCK_EXPLICIT_SITES)
    }

    @Test
    fun clickAllowAllSites_disablesFilter() {
    fun allowAllSites_disablesFilter() {
        setDataStoreValue(1)
        val allowAllSitesWidget = allowAllSitesPreference.createWidget()
        assertThat(allowAllSitesWidget.isChecked).isFalse()
        allowAllSitesWidget.performClick()
        val filterWidget = switchPreference.createWidget()
        assertThat(filterWidget.isChecked).isTrue()
        filterWidget.performClick()

        verifyConfirmSupervisionCredentialsActivity()
        allowAllSitesPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))
        switchPreference.onConfirmCredentials(ActivityResult(Activity.RESULT_OK, null))

        assertThat(allowAllSitesWidget.isChecked).isTrue()
        assertThat(filterWidget.isChecked).isFalse()
        assertThat(getDataStoreValue()).isFalse()
        verify(metricsRule.metricsFeatureProvider).changed(0, allowAllSitesPreference.key, 1)
        verify(metricsRule.metricsFeatureProvider)
            .action(context, ACTION_SUPERVISION_ALLOW_ALL_SITES)
    }

    private fun getDataStoreValue() =
@@ -182,8 +166,8 @@ class SupervisionSafeSitesPreferenceTest {
        SettingsSecureStore.get(context).setInt(BROWSER_CONTENT_FILTERS_ENABLED, value)
    }

    private fun SupervisionSafeSitesPreference.createWidget() =
        createAndBindWidget<SelectorWithWidgetPreference>(context).also { widget ->
    private fun SupervisionSafeSitesSwitchPreference.createWidget() =
        createAndBindWidget<SwitchPreferenceCompat>(context).also { widget ->
            mockLifeCycleContext.stub { on { requirePreference<Preference>(key) } doReturn widget }
        }

Loading