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

Commit 5d7dfdea authored by noshinmir's avatar noshinmir
Browse files

Add Wi-Fi and adaptive mobile network (5G PM) toggle event in Adaptive Connectivity UX

Bug: 393645580
Flag: com.android.settings.flags.enable_nested_toggle_switches
Test: Manual testing
atest AdaptiveConnectivityScreenTest
atest AdaptiveMobileNetworkTogglePreferenceTest
atest WifiScorerTogglePreferenceTest

Change-Id: Ic3b8e4aca5e2096b4e94aed10cd516c3f94e48c1
parent d889fa62
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -37,6 +37,10 @@ class AdaptiveConnectivityScreen : PreferenceScreenCreator {

    override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(context, this) {
        +AdaptiveConnectivityTogglePreference()
        if (Flags.enableNestedToggleSwitches()) {
            +WifiScorerTogglePreference()
            +AdaptiveMobileNetworkTogglePreference()
        }
    }

    override fun hasCompleteHierarchy() = false
+19 −10
Original line number Diff line number Diff line
@@ -15,9 +15,14 @@
 */
package com.android.settings.network;

import static android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED;
import static android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -31,12 +36,7 @@ import com.android.settingslib.search.SearchIndexable;
/** Adaptive connectivity is a feature which automatically manages network connections. */
@SearchIndexable
public class AdaptiveConnectivitySettings extends DashboardFragment {

  private static final String TAG = "AdaptiveConnectivitySettings";
  protected static final String ADAPTIVE_CONNECTIVITY_WIFI_ENABLED =
      "adaptive_connectivity_wifi_enabled";
  protected static final String ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED =
      "adaptive_connectivity_mobile_network_enabled";

  @Override
  public int getMetricsCategory() {
@@ -65,16 +65,25 @@ public class AdaptiveConnectivitySettings extends DashboardFragment {
  public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) {
    Log.i("Settings", "onCreatePreferences");
    super.onCreatePreferences(savedInstanceState, rootKey);
    if (Flags.enableNestedToggleSwitches()) {
      setSwitchVisibility(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, true);
      setSwitchVisibility(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, true);
    if (Flags.enableNestedToggleSwitches() && !isCatalystEnabled()) {
      setupSwitchPreferenceCompat(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED);
      setupSwitchPreferenceCompat(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED);
    }
  }

  private void setSwitchVisibility(String key, boolean isVisible) {
  private void setupSwitchPreferenceCompat(String key) {
    SwitchPreferenceCompat switchPreference = findPreference(key);
    if (switchPreference != null) {
      switchPreference.setVisible(isVisible);
      switchPreference.setOnPreferenceChangeListener(
          (preference, newValue) -> {
            boolean isChecked = (Boolean) newValue;
            Settings.Secure.putInt(getContentResolver(), key, isChecked ? 1 : 0);
            if (preference.getKey().equals(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)) {
              getSystemService(WifiManager.class).setWifiScoringEnabled(isChecked);
            }
            return true;
          });
      switchPreference.setVisible(true);
    }
  }
}
+89 −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.network

import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY
import android.content.Context
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
import com.android.settings.R
import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference

class AdaptiveMobileNetworkTogglePreference() :
    SwitchPreference(
        KEY,
        R.string.adaptive_connectivity_mobile_network_switch_title,
    ),
    PreferenceActionMetricsProvider {

    override val preferenceActionMetrics: Int
        get() = ACTION_ADAPTIVE_CONNECTIVITY

    override val key: String
        get() = KEY

    override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY)

    override fun storage(context: Context): KeyValueStore =
        AdaptiveMobileNetworkToggleStorage(context)

    override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()

    override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions()

    override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
        ReadWritePermit.ALLOW

    override fun getWritePermit(
        context: Context,
        value: Boolean?,
        callingPid: Int,
        callingUid: Int,
    ) = ReadWritePermit.ALLOW

    override val sensitivityLevel
        get() = SensitivityLevel.NO_SENSITIVITY

    @Suppress("UNCHECKED_CAST")
    private class AdaptiveMobileNetworkToggleStorage(
        private val context: Context,
        private val settingsStore: SettingsStore = SettingsSecureStore.get(context),
    ) : KeyValueStoreDelegate {

        override val keyValueStoreDelegate
            get() = settingsStore

        override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) =
            DEFAULT_VALUE as T

        override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
            settingsStore.setValue(key, valueType, value)
        }
    }

    companion object {
        const val KEY = ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
        const val DEFAULT_VALUE = true
    }
}
 No newline at end of file
+101 −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.network

import android.Manifest
import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY
import android.content.Context
import android.net.wifi.WifiManager
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED
import androidx.annotation.RequiresPermission
import com.android.settings.R
import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.datastore.and
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference

class WifiScorerTogglePreference() :
    SwitchPreference(
        KEY,
        R.string.adaptive_connectivity_wifi_switch_title
    ),
    PreferenceActionMetricsProvider {

    override val preferenceActionMetrics: Int
        get() = ACTION_ADAPTIVE_CONNECTIVITY

    override val key: String
        get() = KEY

    override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY)

    override fun storage(context: Context): KeyValueStore =
        WifiScorerToggleStorage(context)

    override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()

    override fun getWritePermissions(context: Context) =
        SettingsSecureStore.getWritePermissions() and Manifest.permission.NETWORK_SETTINGS

    override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
        ReadWritePermit.ALLOW

    override fun getWritePermit(
        context: Context,
        value: Boolean?,
        callingPid: Int,
        callingUid: Int,
    ) = ReadWritePermit.ALLOW

    override val sensitivityLevel
        get() = SensitivityLevel.NO_SENSITIVITY

    @Suppress("UNCHECKED_CAST")
    private class WifiScorerToggleStorage(
        private val context: Context,
        private val settingsStore: SettingsStore = SettingsSecureStore.get(context),
    ) : KeyValueStoreDelegate {

        override val keyValueStoreDelegate
            get() = settingsStore

        override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) =
            DEFAULT_VALUE as T

        @RequiresPermission(Manifest.permission.NETWORK_SETTINGS)
        override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
            settingsStore.setValue(key, valueType, value)
            context
                .getSystemService(WifiManager::class.java)
                ?.setWifiScoringEnabled(
                    (value as Boolean?)
                        ?: DEFAULT_VALUE
                )
        }
    }

    companion object {
        const val KEY = ADAPTIVE_CONNECTIVITY_WIFI_ENABLED
        const val DEFAULT_VALUE = true
    }
}
 No newline at end of file
+121 −13
Original line number Diff line number Diff line
@@ -16,11 +16,18 @@

package com.android.settings.network

import android.content.Context
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.flags.Flags
import com.android.settings.network.AdaptiveConnectivitySettings.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
import com.android.settings.network.AdaptiveConnectivitySettings.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED
import com.android.settingslib.metadata.PreferenceHierarchy
import com.android.settingslib.preference.CatalystScreenTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -28,11 +35,12 @@ import org.junit.runner.RunWith

@Suppress("DEPRECATION")
@RunWith(AndroidJUnit4::class)
class AdaptiveConnectivityScreenTest : CatalystScreenTestCase() {
class AdaptiveConnectivityScreenTest() : CatalystScreenTestCase() {
    override val preferenceScreenCreator = AdaptiveConnectivityScreen()
    override val flagName
        get() = Flags.FLAG_CATALYST_ADAPTIVE_CONNECTIVITY

    private lateinit var fragment: AdaptiveConnectivitySettings
    private val mContext: Context = ApplicationProvider.getApplicationContext()
    override fun migration() {}

    @Test
@@ -40,22 +48,122 @@ class AdaptiveConnectivityScreenTest : CatalystScreenTestCase() {
        assertThat(preferenceScreenCreator.key).isEqualTo(AdaptiveConnectivityScreen.KEY)
    }

    @Test
    fun getPreferenceHierarchy_returnsHierarchy() {
        val hierarchy: PreferenceHierarchy =
            preferenceScreenCreator.getPreferenceHierarchy(mContext)
        (appContext)
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_ENABLED)).isNotNull()
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isNull()
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isNull()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES)
    fun getPreferenceHierarchy_flagEnabled_returnsHierarchyWithNestedToggle() {
        val hierarchy: PreferenceHierarchy =
            preferenceScreenCreator.getPreferenceHierarchy(mContext)
        (appContext)
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_ENABLED)).isNotNull()
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isNotNull()
        assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isNotNull()

    }

    @Test
    fun flagDefaultDisabled_noSwitchPreferenceCompatExists() {
        // create fragment
        val fragment: AdaptiveConnectivitySettings =
            preferenceScreenCreator.fragmentClass().newInstance()
        // check if switch preference exists
        assertSwitchPreferenceCompatIsNull(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment)
        assertSwitchPreferenceCompatIsNull(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, fragment)
        val scenario = launchFragmentInContainer<AdaptiveConnectivitySettings>()
        scenario.onFragment { fragment ->
            this.fragment = fragment
            assertSwitchPreferenceCompatVisibility(
                ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment,
                false
            )
            assertSwitchPreferenceCompatVisibility(
                ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED,
                fragment,
                false
            )
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES)
    fun flagEnabled_switchPreferenceCompatExists() {
        val scenario = launchFragmentInContainer<AdaptiveConnectivitySettings>()
        scenario.onFragment { fragment ->
            this.fragment = fragment
            assertSwitchPreferenceCompatVisibility(
                ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment,
                true
            )
            assertSwitchPreferenceCompatVisibility(
                ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED,
                fragment,
                true
            )
        }
    }


    @Test
    @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES)
    fun flagEnabled_onWifiScorerSwitchClick_shouldUpdateSetting() {
        val scenario = launchFragmentInContainer<AdaptiveConnectivitySettings>()
        scenario.onFragment { fragment: AdaptiveConnectivitySettings ->
            this.fragment = fragment
            val switchPreference =
                fragment.findPreference<SwitchPreferenceCompat>(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)
            assertThat(switchPreference?.isChecked).isTrue()
            switchPreference?.performClick()
            assertThat(switchPreference?.isChecked).isFalse()
            assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isFalse()
            switchPreference?.performClick()
            assertThat(switchPreference?.isChecked).isTrue()
            assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isTrue()
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES)
    fun flagEnabled_onAdaptiveMobileNetworkSwitchClick_shouldUpdateSetting() {
        val scenario = launchFragmentInContainer<AdaptiveConnectivitySettings>()
        scenario.onFragment { fragment: AdaptiveConnectivitySettings ->
            this.fragment = fragment
            val switchPreference =
                fragment.findPreference<SwitchPreferenceCompat>(
                    ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
                )
            assertThat(switchPreference?.isChecked).isTrue()
            switchPreference?.performClick()
            assertThat(switchPreference?.isChecked).isFalse()
            assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isFalse()
            switchPreference?.performClick()
            assertThat(switchPreference?.isChecked).isTrue()
            assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isTrue()
        }
    }

    /**
     * Helper function to get the setting value from Settings.Secure.
     *
     * @param key the key of the setting to get.
     */
    private fun updateSetting(key: String): Boolean {
        return (Settings.Secure.getInt(
            mContext.contentResolver,
            key,
            0
        ) == 1)
    }

    private fun assertSwitchPreferenceCompatIsNull(
    private fun assertSwitchPreferenceCompatVisibility(
        key: String,
        fragment: AdaptiveConnectivitySettings
        fragment: AdaptiveConnectivitySettings,
        isVisible: Boolean
    ) {
        val switchPreference = fragment.findPreference<SwitchPreferenceCompat>(key)
        assertThat(switchPreference).isNull()
        assertThat(switchPreference?.isVisible).isEqualTo(isVisible)
    }

}
Loading