[Catalyst] Migrate Wi-Fi Hotspot preference

Bug: 368359963
Flag: com.android.settings.flags.catalyst_tether_settings
Test: Manual testing
atest -c TetherSettingsTest \
         TetherScreenTest

Change-Id: I89d418454af7887a4892c616f4efff481b536a91
This commit is contained in:
Weng Su
2024-12-03 04:03:06 +00:00
parent cfd023dc1f
commit 6fb75678e5
6 changed files with 261 additions and 7 deletions

View File

@@ -28,8 +28,7 @@
android:title="@string/wifi_hotspot_checkbox_text" android:title="@string/wifi_hotspot_checkbox_text"
android:summary="@string/wifi_hotspot_off_subtext" android:summary="@string/wifi_hotspot_off_subtext"
android:fragment="com.android.settings.wifi.tether.WifiTetherSettings" android:fragment="com.android.settings.wifi.tether.WifiTetherSettings"
settings:allowDividerAbove="true" settings:allowDividerAbove="true"/>
settings:maxLines="2"/>
<com.android.settingslib.RestrictedSwitchPreference <com.android.settingslib.RestrictedSwitchPreference
android:key="usb_tether_settings" android:key="usb_tether_settings"

View File

@@ -22,6 +22,7 @@ import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R import com.android.settings.R
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.android.settings.network.TetherPreferenceController import com.android.settings.network.TetherPreferenceController
import com.android.settings.wifi.tether.WifiHotspotSwitchPreference
import com.android.settingslib.TetherUtil import com.android.settingslib.TetherUtil
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceAvailabilityProvider
@@ -63,7 +64,9 @@ class TetherScreen :
override fun fragmentClass() = TetherSettings::class.java override fun fragmentClass() = TetherSettings::class.java
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {
+WifiHotspotSwitchPreference(context)
}
companion object { companion object {
const val KEY = "tether_settings" const val KEY = "tether_settings"

View File

@@ -203,7 +203,9 @@ public class TetherSettings extends RestrictedDashboardFragment
getPreferenceScreen().removePreference(mUsbTether); getPreferenceScreen().removePreference(mUsbTether);
} }
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen()); if (!isCatalystEnabled() && mWifiTetherPreferenceController != null) {
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
}
if (!bluetoothAvailable) { if (!bluetoothAvailable) {
getPreferenceScreen().removePreference(mBluetoothTether); getPreferenceScreen().removePreference(mBluetoothTether);
@@ -223,8 +225,10 @@ public class TetherSettings extends RestrictedDashboardFragment
@VisibleForTesting @VisibleForTesting
void setupViewModel() { void setupViewModel() {
TetheringManagerModel model = new ViewModelProvider(this).get(TetheringManagerModel.class); TetheringManagerModel model = new ViewModelProvider(this).get(TetheringManagerModel.class);
mWifiTetherPreferenceController = if (!isCatalystEnabled()) {
new WifiTetherPreferenceController(getContext(), getSettingsLifecycle(), model); mWifiTetherPreferenceController =
new WifiTetherPreferenceController(getContext(), getSettingsLifecycle(), model);
}
mTm = model.getTetheringManager(); mTm = model.getTetheringManager();
model.getTetheredInterfaces().observe(this, this::onTetheredInterfacesChanged); model.getTetheredInterfaces().observe(this, this::onTetheredInterfacesChanged);
} }
@@ -261,7 +265,9 @@ public class TetherSettings extends RestrictedDashboardFragment
@Override @Override
public void onDataSaverChanged(boolean isDataSaving) { public void onDataSaverChanged(boolean isDataSaving) {
mDataSaverEnabled = isDataSaving; mDataSaverEnabled = isDataSaving;
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled); if (!isCatalystEnabled()) {
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled);
}
mUsbTether.setEnabled(!mDataSaverEnabled); mUsbTether.setEnabled(!mDataSaverEnabled);
mBluetoothTether.setEnabled(!mDataSaverEnabled); mBluetoothTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setEnabled(!mDataSaverEnabled); mEthernetTether.setEnabled(!mDataSaverEnabled);

View File

@@ -0,0 +1,241 @@
/*
* Copyright (C) 2024 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.wifi.tether
import android.app.settings.SettingsEnums
import android.content.Context
import android.content.Intent
import android.net.TetheringManager
import android.net.wifi.WifiClient
import android.net.wifi.WifiManager
import android.os.UserManager
import android.text.BidiFormatter
import android.util.Log
import androidx.preference.Preference
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.core.SubSettingLauncher
import com.android.settings.datausage.DataSaverBackend
import com.android.settings.wifi.WifiUtils.canShowWifiHotspot
import com.android.settingslib.PrimarySwitchPreference
import com.android.settingslib.TetherUtil
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyValueStore
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.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
import com.android.settingslib.preference.PreferenceBinding
import com.android.settingslib.wifi.WifiUtils.Companion.getWifiTetherSummaryForConnectedDevices
// LINT.IfChange
@Deprecated("Deprecated in Java")
@Suppress("MissingPermission", "NewApi", "UNCHECKED_CAST")
class WifiHotspotSwitchPreference(context: Context) :
SwitchPreference(KEY, R.string.wifi_hotspot_checkbox_text),
PreferenceBinding,
PreferenceAvailabilityProvider,
PreferenceSummaryProvider,
PreferenceLifecycleProvider,
PreferenceRestrictionMixin {
private val wifiHotspotStore = WifiHotspotStore(context)
private val dataSaverBackend = DataSaverBackend(context)
private var dataSaverBackendListener: DataSaverBackend.Listener? = null
override fun isAvailable(context: Context) =
canShowWifiHotspot(context) &&
TetherUtil.isTetherAvailable(context) &&
!Utils.isMonkeyRunning()
override fun getSummary(context: Context): CharSequence? =
when (wifiHotspotStore.sapState) {
WifiManager.WIFI_AP_STATE_ENABLING -> context.getString(R.string.wifi_tether_starting)
WifiManager.WIFI_AP_STATE_ENABLED ->
when (wifiHotspotStore.sapClientsSize) {
null ->
context.getString(
R.string.wifi_tether_enabled_subtext,
BidiFormatter.getInstance()
.unicodeWrap(context.getSoftApConfiguration()?.ssid),
)
else ->
getWifiTetherSummaryForConnectedDevices(
context,
wifiHotspotStore.sapClientsSize!!,
)
}
WifiManager.WIFI_AP_STATE_DISABLING -> context.getString(R.string.wifi_tether_stopping)
WifiManager.WIFI_AP_STATE_DISABLED ->
context.getString(R.string.wifi_hotspot_off_subtext)
else ->
when (wifiHotspotStore.sapFailureReason) {
WifiManager.SAP_START_FAILURE_NO_CHANNEL ->
context.getString(R.string.wifi_sap_no_channel_error)
else -> context.getString(R.string.wifi_error)
}
}
override fun intent(context: Context): Intent? =
SubSettingLauncher(context)
.apply {
setDestination(WifiTetherSettings::class.java.name)
setTitleRes(R.string.wifi_hotspot_checkbox_text)
setSourceMetricsCategory(SettingsEnums.WIFI_TETHER_SETTINGS)
}
.toIntent()
override fun isEnabled(context: Context) =
!dataSaverBackend.isDataSaverEnabled && super<PreferenceRestrictionMixin>.isEnabled(context)
override val restrictionKeys
get() = arrayOf(UserManager.DISALLOW_WIFI_TETHERING)
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
when {
dataSaverBackend.isDataSaverEnabled -> ReadWritePermit.DISALLOW
else -> ReadWritePermit.ALLOW
}
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
override fun createWidget(context: Context) = PrimarySwitchPreference(context)
override fun storage(context: Context): KeyValueStore = wifiHotspotStore
private class WifiHotspotStore(private val context: Context) :
AbstractKeyedDataObservable<String>(), KeyValueStore {
private var wifiTetherSoftApManager: WifiTetherSoftApManager? = null
var sapState: Int = WifiManager.WIFI_AP_STATE_DISABLED
var sapFailureReason: Int? = null
var sapClientsSize: Int? = null
override fun contains(key: String) =
key == KEY && context.wifiManager != null && context.tetheringManager != null
override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
val wifiApState = context.wifiManager?.wifiApState
val value =
wifiApState == WifiManager.WIFI_AP_STATE_ENABLING ||
wifiApState == WifiManager.WIFI_AP_STATE_ENABLED
return value as T?
}
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value !is Boolean) return
context.tetheringManager?.let {
if (value) {
val startTetheringCallback =
object : TetheringManager.StartTetheringCallback {
override fun onTetheringStarted() {
Log.d(TAG, "onTetheringStarted()")
}
override fun onTetheringFailed(error: Int) {
Log.e(TAG, "onTetheringFailed(),error=$error")
}
}
it.startTethering(
TetheringManager.TETHERING_WIFI,
HandlerExecutor.main,
startTetheringCallback,
)
} else {
it.stopTethering(TetheringManager.TETHERING_WIFI)
}
}
}
override fun onFirstObserverAdded() {
val wifiSoftApCallback =
object : WifiTetherSoftApManager.WifiTetherSoftApCallback {
override fun onStateChanged(state: Int, failureReason: Int) {
Log.d(TAG, "onStateChanged(),state=$state,failureReason=$failureReason")
sapState = state
sapFailureReason = failureReason
if (state == WifiManager.WIFI_AP_STATE_DISABLED) sapClientsSize = null
notifyChange(KEY, DataChangeReason.UPDATE)
}
override fun onConnectedClientsChanged(clients: List<WifiClient>?) {
sapClientsSize = clients?.size ?: 0
Log.d(TAG, "onConnectedClientsChanged(),sapClientsSize=$sapClientsSize")
notifyChange(KEY, DataChangeReason.UPDATE)
}
}
wifiTetherSoftApManager =
WifiTetherSoftApManager(context.wifiManager, wifiSoftApCallback)
wifiTetherSoftApManager?.registerSoftApCallback()
}
override fun onLastObserverRemoved() {
wifiTetherSoftApManager?.unRegisterSoftApCallback()
}
}
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
(preference as PrimarySwitchPreference).apply {
isChecked = preferenceDataStore!!.getBoolean(key, false)
}
}
override fun onStart(context: PreferenceLifecycleContext) {
val listener =
DataSaverBackend.Listener { isDataSaving: Boolean ->
context.findPreference<PrimarySwitchPreference>(KEY)?.isSwitchEnabled =
!isDataSaving
context.notifyPreferenceChange(KEY)
}
dataSaverBackendListener = listener
dataSaverBackend.addListener(listener)
}
override fun onStop(context: PreferenceLifecycleContext) {
dataSaverBackendListener?.let {
dataSaverBackend.remListener(it)
dataSaverBackendListener = null
}
}
companion object {
const val TAG = "WifiHotspotSwitchPreference"
const val KEY = "wifi_tether"
private val Context.wifiManager: WifiManager?
get() = applicationContext.getSystemService(WifiManager::class.java)
private fun Context.getSoftApConfiguration() = wifiManager?.softApConfiguration
private val Context.tetheringManager: TetheringManager?
get() = applicationContext.getSystemService(TetheringManager::class.java)
}
}
// LINT.ThenChange(WifiTetherPreferenceController.java)

View File

@@ -48,6 +48,7 @@ import com.android.settingslib.wifi.WifiUtils;
import java.util.List; import java.util.List;
// LINT.IfChange
public class WifiTetherPreferenceController extends AbstractPreferenceController public class WifiTetherPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop, implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop,
SwitchWidgetController.OnSwitchChangeListener { SwitchWidgetController.OnSwitchChangeListener {
@@ -251,3 +252,4 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController
return true; return true;
} }
} }
// LINT.ThenChange(WifiHotspotSwitchPreference.kt)

View File

@@ -40,6 +40,9 @@ class TetherScreenTest : CatalystScreenTestCase() {
override val flagName: String override val flagName: String
get() = Flags.FLAG_CATALYST_TETHER_SETTINGS get() = Flags.FLAG_CATALYST_TETHER_SETTINGS
// TODO: Remove override (See b/368359963#comment7)
override fun migration() {}
@Before @Before
fun setUp() { fun setUp() {
ShadowConnectivityManager.getShadow().setTetheringSupported(true) ShadowConnectivityManager.getShadow().setTetheringSupported(true)