[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:
@@ -28,8 +28,7 @@
|
||||
android:title="@string/wifi_hotspot_checkbox_text"
|
||||
android:summary="@string/wifi_hotspot_off_subtext"
|
||||
android:fragment="com.android.settings.wifi.tether.WifiTetherSettings"
|
||||
settings:allowDividerAbove="true"
|
||||
settings:maxLines="2"/>
|
||||
settings:allowDividerAbove="true"/>
|
||||
|
||||
<com.android.settingslib.RestrictedSwitchPreference
|
||||
android:key="usb_tether_settings"
|
||||
|
@@ -22,6 +22,7 @@ import com.android.settings.PreferenceRestrictionMixin
|
||||
import com.android.settings.R
|
||||
import com.android.settings.flags.Flags
|
||||
import com.android.settings.network.TetherPreferenceController
|
||||
import com.android.settings.wifi.tether.WifiHotspotSwitchPreference
|
||||
import com.android.settingslib.TetherUtil
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
|
||||
@@ -63,7 +64,9 @@ class TetherScreen :
|
||||
|
||||
override fun fragmentClass() = TetherSettings::class.java
|
||||
|
||||
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {}
|
||||
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {
|
||||
+WifiHotspotSwitchPreference(context)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY = "tether_settings"
|
||||
|
@@ -203,7 +203,9 @@ public class TetherSettings extends RestrictedDashboardFragment
|
||||
getPreferenceScreen().removePreference(mUsbTether);
|
||||
}
|
||||
|
||||
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
|
||||
if (!isCatalystEnabled() && mWifiTetherPreferenceController != null) {
|
||||
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
|
||||
}
|
||||
|
||||
if (!bluetoothAvailable) {
|
||||
getPreferenceScreen().removePreference(mBluetoothTether);
|
||||
@@ -223,8 +225,10 @@ public class TetherSettings extends RestrictedDashboardFragment
|
||||
@VisibleForTesting
|
||||
void setupViewModel() {
|
||||
TetheringManagerModel model = new ViewModelProvider(this).get(TetheringManagerModel.class);
|
||||
mWifiTetherPreferenceController =
|
||||
new WifiTetherPreferenceController(getContext(), getSettingsLifecycle(), model);
|
||||
if (!isCatalystEnabled()) {
|
||||
mWifiTetherPreferenceController =
|
||||
new WifiTetherPreferenceController(getContext(), getSettingsLifecycle(), model);
|
||||
}
|
||||
mTm = model.getTetheringManager();
|
||||
model.getTetheredInterfaces().observe(this, this::onTetheredInterfacesChanged);
|
||||
}
|
||||
@@ -261,7 +265,9 @@ public class TetherSettings extends RestrictedDashboardFragment
|
||||
@Override
|
||||
public void onDataSaverChanged(boolean isDataSaving) {
|
||||
mDataSaverEnabled = isDataSaving;
|
||||
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled);
|
||||
if (!isCatalystEnabled()) {
|
||||
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled);
|
||||
}
|
||||
mUsbTether.setEnabled(!mDataSaverEnabled);
|
||||
mBluetoothTether.setEnabled(!mDataSaverEnabled);
|
||||
mEthernetTether.setEnabled(!mDataSaverEnabled);
|
||||
|
@@ -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)
|
@@ -48,6 +48,7 @@ import com.android.settingslib.wifi.WifiUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// LINT.IfChange
|
||||
public class WifiTetherPreferenceController extends AbstractPreferenceController
|
||||
implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop,
|
||||
SwitchWidgetController.OnSwitchChangeListener {
|
||||
@@ -251,3 +252,4 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// LINT.ThenChange(WifiHotspotSwitchPreference.kt)
|
||||
|
@@ -40,6 +40,9 @@ class TetherScreenTest : CatalystScreenTestCase() {
|
||||
override val flagName: String
|
||||
get() = Flags.FLAG_CATALYST_TETHER_SETTINGS
|
||||
|
||||
// TODO: Remove override (See b/368359963#comment7)
|
||||
override fun migration() {}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ShadowConnectivityManager.getShadow().setTetheringSupported(true)
|
||||
|
Reference in New Issue
Block a user