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

Commit 4d0be536 authored by Xiaomiao Zhang's avatar Xiaomiao Zhang
Browse files

Create data store for supervision safe sites preference.

Test: atest SupervisionWebContentFiltersScreenTest
Test: atest SupervisionSafeSitesPreferenceTest
Test: deployed locally to a physical device
Flag: android.app.supervision.flags.enable_web_content_filters_screen
Bug: 401568468
Change-Id: I7fe8a9c5932b4c8f63c4067ba6914eb73d0e2373
parent a9ef3307
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settings.supervision

import android.content.Context
import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObserver
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore

/** Datastore of the safe sites preference. */
@Suppress("UNCHECKED_CAST")
class SupervisionSafeSitesDataStore(
    private val context: Context,
    private val settingsStore: SettingsStore = SettingsSecureStore.get(context),
) : AbstractKeyedDataObservable<String>(), KeyedObserver<String>, KeyValueStore {

    override fun contains(key: String) =
        key == SupervisionBlockExplicitSitesPreference.KEY ||
            key == SupervisionAllowAllSitesPreference.KEY

    override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
        val settingValue = (settingsStore.getBoolean(BROWSER_CONTENT_FILTERS_ENABLED) == true)
        return when (key) {
            SupervisionAllowAllSitesPreference.KEY -> !settingValue

            SupervisionBlockExplicitSitesPreference.KEY -> settingValue

            else -> null
        }
            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
        settingsStore.addObserver(BROWSER_CONTENT_FILTERS_ENABLED, this, HandlerExecutor.main)
    }

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

    override fun onLastObserverRemoved() {
        settingsStore.removeObserver(BROWSER_CONTENT_FILTERS_ENABLED, this)
    }
}
+9 −9
Original line number Diff line number Diff line
@@ -18,9 +18,7 @@ package com.android.settings.supervision
import android.content.Context
import androidx.preference.Preference
import com.android.settings.R
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.Permissions
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ReadWritePermit
@@ -30,9 +28,10 @@ import com.android.settingslib.preference.forEachRecursively
import com.android.settingslib.widget.SelectorWithWidgetPreference

/** Base class of web content filters Safe sites preferences. */
sealed class SupervisionSafeSitesPreference :
    BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding {
    override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context)
sealed class SupervisionSafeSitesPreference(
    protected val dataStore: SupervisionSafeSitesDataStore
) : BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding {
    override fun storage(context: Context) = dataStore

    override fun getReadPermissions(context: Context) = Permissions.EMPTY

@@ -64,15 +63,15 @@ sealed class SupervisionSafeSitesPreference :
    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
        super.bind(preference, metadata)
        (preference as SelectorWithWidgetPreference).also {
            // TODO(b/401568468): Set the isChecked value using stored values.
            it.isChecked = (it.key == SupervisionAllowAllSitesPreference.KEY)
            it.isChecked = (dataStore.getBoolean(it.key) == true)
            it.setOnClickListener(this)
        }
    }
}

/** The "Try to block explicit sites" preference. */
class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() {
class SupervisionBlockExplicitSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
    SupervisionSafeSitesPreference(dataStore) {

    override val key
        get() = KEY
@@ -89,7 +88,8 @@ class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference()
}

/** The "Allow all sites" preference. */
class SupervisionAllowAllSitesPreference : SupervisionSafeSitesPreference() {
class SupervisionAllowAllSitesPreference(dataStore: SupervisionSafeSitesDataStore) :
    SupervisionSafeSitesPreference(dataStore) {

    override val key
        get() = KEY
+3 −2
Original line number Diff line number Diff line
@@ -47,8 +47,9 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator {
                R.string.supervision_web_content_filters_browser_title,
            ) +=
                {
                    +SupervisionBlockExplicitSitesPreference()
                    +SupervisionAllowAllSitesPreference()
                    val dataStore = SupervisionSafeSitesDataStore(context)
                    +SupervisionBlockExplicitSitesPreference(dataStore)
                    +SupervisionAllowAllSitesPreference(dataStore)
                }
            // TODO(b/401569571) implement the SafeSearch group.
        }
+76 −3
Original line number Diff line number Diff line
@@ -16,20 +16,33 @@
package com.android.settings.supervision

import android.content.Context
import android.provider.Settings
import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED
import android.provider.Settings.SettingNotFoundException
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settingslib.preference.createAndBindWidget
import com.android.settingslib.widget.SelectorWithWidgetPreference
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SupervisionSafeSitesPreferenceTest {
    private val context: Context = ApplicationProvider.getApplicationContext()
    private lateinit var dataStore: SupervisionSafeSitesDataStore
    private lateinit var allowAllSitesPreference: SupervisionAllowAllSitesPreference
    private lateinit var blockExplicitSitesPreference: SupervisionBlockExplicitSitesPreference

    private val allowAllSitesPreference = SupervisionAllowAllSitesPreference()

    private val blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference()
    @Before
    fun setUp() {
        dataStore = SupervisionSafeSitesDataStore(context)
        allowAllSitesPreference = SupervisionAllowAllSitesPreference(dataStore)
        blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference(dataStore)
    }

    @Test
    fun getTitle_allowAllSites() {
@@ -50,4 +63,64 @@ class SupervisionSafeSitesPreferenceTest {
                R.string.supervision_web_content_filters_browser_block_explicit_sites_summary
            )
    }

    @Test
    fun allowAllSitesIsChecked_whenNoValueIsSet() {
        assertThrows(SettingNotFoundException::class.java) {
            Settings.Secure.getInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED)
        }
        assertThat(getBlockExplicitSitesWidget().isChecked).isFalse()
        assertThat(getAllowAllSitesWidget().isChecked).isTrue()
    }

    @Test
    fun blockExplicitSitesIsChecked_whenPreviouslyEnabled() {
        Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1)
        assertThat(getAllowAllSitesWidget().isChecked).isFalse()
        assertThat(getBlockExplicitSitesWidget().isChecked).isTrue()
    }

    @Test
    fun clickBlockExplicitSites_enablesFilter() {
        Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 0)
        val blockExplicitSitesWidget = getBlockExplicitSitesWidget()
        assertThat(blockExplicitSitesWidget.isChecked).isFalse()

        blockExplicitSitesWidget.performClick()

        assertThat(
            Settings.Secure.getInt(
                context.getContentResolver(),
                BROWSER_CONTENT_FILTERS_ENABLED,
            )
        )
            .isEqualTo(1)
        assertThat(blockExplicitSitesWidget.isChecked).isTrue()
    }

    @Test
    fun clickAllowAllSites_disablesFilter() {
        Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1)
        val allowAllSitesWidget = getAllowAllSitesWidget()
        assertThat(allowAllSitesWidget.isChecked).isFalse()

        allowAllSitesWidget.performClick()

        assertThat(
            Settings.Secure.getInt(
                context.getContentResolver(),
                BROWSER_CONTENT_FILTERS_ENABLED,
            )
        )
            .isEqualTo(0)
        assertThat(allowAllSitesWidget.isChecked).isTrue()
    }

    private fun getBlockExplicitSitesWidget(): SelectorWithWidgetPreference {
        return blockExplicitSitesPreference.createAndBindWidget(context)
    }

    private fun getAllowAllSitesWidget(): SelectorWithWidgetPreference {
        return allowAllSitesPreference.createAndBindWidget(context)
    }
}
+51 −1
Original line number Diff line number Diff line
@@ -15,18 +15,32 @@
 */
package com.android.settings.supervision

import android.app.supervision.flags.Flags
import android.content.Context
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.fragment.app.testing.FragmentScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settingslib.widget.SelectorWithWidgetPreference
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SupervisionWebContentFiltersScreenTest {
    @get:Rule val setFlagsRule = SetFlagsRule()
    private val context: Context = ApplicationProvider.getApplicationContext()
    private val supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen()
    private lateinit var supervisionWebContentFiltersScreen: SupervisionWebContentFiltersScreen

    @Before
    fun setUp() {
        supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen()
    }

    @Test
    fun key() {
@@ -39,4 +53,40 @@ class SupervisionWebContentFiltersScreenTest {
        assertThat(supervisionWebContentFiltersScreen.title)
            .isEqualTo(R.string.supervision_web_content_filters_title)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
    fun flagEnabled() {
        assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isTrue()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
    fun flagDisabled() {
        assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isFalse()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN)
    fun switchSafeSitesPreferences() {
        FragmentScenario.launchInContainer(supervisionWebContentFiltersScreen.fragmentClass())
            .onFragment { fragment ->
                val allowAllSitesPreference =
                    fragment.findPreference<SelectorWithWidgetPreference>(
                        SupervisionAllowAllSitesPreference.KEY
                    )!!
                val blockExplicitSitesPreference =
                    fragment.findPreference<SelectorWithWidgetPreference>(
                        SupervisionBlockExplicitSitesPreference.KEY
                    )!!

                assertThat(allowAllSitesPreference.isChecked).isTrue()
                assertThat(blockExplicitSitesPreference.isChecked).isFalse()

                blockExplicitSitesPreference.performClick()

                assertThat(blockExplicitSitesPreference.isChecked).isTrue()
                assertThat(allowAllSitesPreference.isChecked).isFalse()
            }
    }
}