From 6fb75678e5091e66a675182cf61e6adf596e7835 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Tue, 3 Dec 2024 04:03:06 +0000 Subject: [PATCH 1/2] [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 --- res/xml/tether_prefs.xml | 3 +- .../settings/network/tether/TetherScreen.kt | 5 +- .../network/tether/TetherSettings.java | 14 +- .../tether/WifiHotspotSwitchPreference.kt | 241 ++++++++++++++++++ .../WifiTetherPreferenceController.java | 2 + .../network/tether/TetherScreenTest.kt | 3 + 6 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt diff --git a/res/xml/tether_prefs.xml b/res/xml/tether_prefs.xml index 89bd631bcd2..c3ea3cbcd8b 100644 --- a/res/xml/tether_prefs.xml +++ b/res/xml/tether_prefs.xml @@ -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"/> 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.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(), 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 getValue(key: String, valueType: Class): 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 setValue(key: String, valueType: Class, 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?) { + 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(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) diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java index 0baac2c063a..d5d075106c5 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java +++ b/src/com/android/settings/wifi/tether/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) diff --git a/tests/robotests/src/com/android/settings/network/tether/TetherScreenTest.kt b/tests/robotests/src/com/android/settings/network/tether/TetherScreenTest.kt index 0eeac4353f1..f3d1d394e30 100644 --- a/tests/robotests/src/com/android/settings/network/tether/TetherScreenTest.kt +++ b/tests/robotests/src/com/android/settings/network/tether/TetherScreenTest.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) From 572afa59e6a1628c8ac42e76604b9b0bb2807ef0 Mon Sep 17 00:00:00 2001 From: Yiling Chuang Date: Fri, 6 Dec 2024 11:03:51 +0000 Subject: [PATCH 2/2] Fix the unclickable learn more link. The UsageProgressBarPreference is replaced with BatteryHeaderTextPreference, and the movement method of the textview was not added, which leads to the unclickable link. Hence, add back the movement method of the textview to allow the linkable text. Fixes: 382395040 Test: manual test Flag: EXEMPT bugfix Change-Id: I6dd5b2c2fbec891035fa8b0fefa47de6962324b7 --- .../android/settings/fuelgauge/BatteryHeaderTextPreference.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java b/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java index 516cc711657..a809a5682c8 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge; import android.content.Context; import android.text.TextUtils; +import android.text.method.LinkMovementMethod; import android.util.AttributeSet; import android.widget.TextView; @@ -44,6 +45,7 @@ public class BatteryHeaderTextPreference extends Preference implements GroupSect public void onBindViewHolder(PreferenceViewHolder view) { final TextView textView = (TextView) view.findViewById(R.id.text); textView.setText(mText); + textView.setMovementMethod(LinkMovementMethod.getInstance()); if (!TextUtils.isEmpty(mContentDescription)) { textView.setContentDescription(mContentDescription); }