1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.wifi.tether 18 19 import android.Manifest 20 import android.app.settings.SettingsEnums.ACTION_WIFI_HOTSPOT 21 import android.app.settings.SettingsEnums.WIFI_TETHER_SETTINGS 22 import android.content.Context 23 import android.content.Intent 24 import android.net.TetheringManager 25 import android.net.TetheringManager.TETHERING_WIFI 26 import android.net.wifi.WifiClient 27 import android.net.wifi.WifiManager 28 import android.os.UserManager 29 import android.text.BidiFormatter 30 import android.util.Log 31 import com.android.settings.R 32 import com.android.settings.Utils 33 import com.android.settings.contract.KEY_WIFI_HOTSPOT 34 import com.android.settings.core.SubSettingLauncher 35 import com.android.settings.datausage.DataSaverMainSwitchPreference.Companion.KEY as DATA_SAVER_KEY 36 import com.android.settings.metrics.PreferenceActionMetricsProvider 37 import com.android.settings.restriction.PreferenceRestrictionMixin 38 import com.android.settings.wifi.WifiUtils.canShowWifiHotspot 39 import com.android.settings.wifi.utils.tetheringManager 40 import com.android.settings.wifi.utils.wifiApState 41 import com.android.settings.wifi.utils.wifiManager 42 import com.android.settings.wifi.utils.wifiSoftApSsid 43 import com.android.settingslib.PrimarySwitchPreferenceBinding 44 import com.android.settingslib.TetherUtil 45 import com.android.settingslib.datastore.AbstractKeyedDataObservable 46 import com.android.settingslib.datastore.HandlerExecutor 47 import com.android.settingslib.datastore.KeyValueStore 48 import com.android.settingslib.datastore.KeyedObserver 49 import com.android.settingslib.datastore.Permissions 50 import com.android.settingslib.metadata.PreferenceAvailabilityProvider 51 import com.android.settingslib.metadata.PreferenceChangeReason 52 import com.android.settingslib.metadata.PreferenceSummaryProvider 53 import com.android.settingslib.metadata.ReadWritePermit 54 import com.android.settingslib.metadata.SensitivityLevel 55 import com.android.settingslib.metadata.SwitchPreference 56 import com.android.settingslib.wifi.WifiUtils.Companion.getWifiTetherSummaryForConnectedDevices 57 58 // LINT.IfChange 59 class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStore) : 60 SwitchPreference(KEY, R.string.wifi_hotspot_checkbox_text), 61 PrimarySwitchPreferenceBinding, 62 PreferenceActionMetricsProvider, 63 PreferenceAvailabilityProvider, 64 PreferenceSummaryProvider, 65 PreferenceRestrictionMixin { 66 67 override val preferenceActionMetrics: Int 68 get() = ACTION_WIFI_HOTSPOT 69 tagsnull70 override fun tags(context: Context) = arrayOf(KEY_WIFI_HOTSPOT) 71 72 private val wifiHotspotStore = WifiHotspotStore(context, dataSaverStore) 73 74 override fun isAvailable(context: Context) = 75 canShowWifiHotspot(context) && 76 TetherUtil.isTetherAvailable(context) && 77 !Utils.isMonkeyRunning() 78 79 override fun getSummary(context: Context): CharSequence? = 80 when (context.wifiApState) { 81 WifiManager.WIFI_AP_STATE_ENABLING -> context.getString(R.string.wifi_tether_starting) 82 WifiManager.WIFI_AP_STATE_ENABLED -> { 83 val sapClientsSize = wifiHotspotStore.sapClientsSize 84 if (sapClientsSize == null) { 85 context.getString( 86 R.string.wifi_tether_enabled_subtext, 87 BidiFormatter.getInstance().unicodeWrap(context.wifiSoftApSsid), 88 ) 89 } else { 90 getWifiTetherSummaryForConnectedDevices(context, sapClientsSize) 91 } 92 } 93 WifiManager.WIFI_AP_STATE_DISABLING -> context.getString(R.string.wifi_tether_stopping) 94 WifiManager.WIFI_AP_STATE_DISABLED -> 95 context.getString(R.string.wifi_hotspot_off_subtext) 96 else -> 97 when (wifiHotspotStore.sapFailureReason) { 98 WifiManager.SAP_START_FAILURE_NO_CHANNEL -> 99 context.getString(R.string.wifi_sap_no_channel_error) 100 else -> context.getString(R.string.wifi_error) 101 } 102 } 103 intentnull104 override fun intent(context: Context): Intent? = 105 SubSettingLauncher(context) 106 .apply { 107 setDestination(WifiTetherSettings::class.java.name) 108 setTitleRes(R.string.wifi_hotspot_checkbox_text) 109 setSourceMetricsCategory(WIFI_TETHER_SETTINGS) 110 } 111 .toIntent() 112 isEnablednull113 override fun isEnabled(context: Context) = 114 wifiHotspotStore.dataSaverStore.getBoolean(DATA_SAVER_KEY) != true && 115 super<PreferenceRestrictionMixin>.isEnabled(context) 116 117 override val restrictionKeys 118 get() = arrayOf(UserManager.DISALLOW_WIFI_TETHERING) 119 120 override fun getReadPermissions(context: Context) = 121 Permissions.allOf(Manifest.permission.ACCESS_WIFI_STATE) 122 123 override fun getWritePermissions(context: Context) = 124 Permissions.allOf(Manifest.permission.TETHER_PRIVILEGED) 125 126 override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = 127 ReadWritePermit.ALLOW 128 129 override fun getWritePermit( 130 context: Context, 131 value: Boolean?, 132 callingPid: Int, 133 callingUid: Int, 134 ) = ReadWritePermit.ALLOW 135 136 override val sensitivityLevel 137 get() = SensitivityLevel.HIGH_SENSITIVITY 138 139 override fun storage(context: Context): KeyValueStore = wifiHotspotStore 140 141 @Suppress("UNCHECKED_CAST") 142 private class WifiHotspotStore( 143 private val context: Context, 144 val dataSaverStore: KeyValueStore, 145 ) : 146 AbstractKeyedDataObservable<String>(), 147 KeyValueStore, 148 WifiTetherSoftApManager.WifiTetherSoftApCallback, 149 TetheringManager.StartTetheringCallback, 150 KeyedObserver<String> { 151 152 private var wifiTetherSoftApManager: WifiTetherSoftApManager? = null 153 var sapFailureReason: Int? = null 154 var sapClientsSize: Int? = null 155 156 override fun contains(key: String) = 157 key == KEY && context.wifiManager != null && context.tetheringManager != null 158 159 override fun <T : Any> getValue(key: String, valueType: Class<T>): T? { 160 val wifiApState = context.wifiApState 161 val value = 162 wifiApState == WifiManager.WIFI_AP_STATE_ENABLING || 163 wifiApState == WifiManager.WIFI_AP_STATE_ENABLED 164 return value as T? 165 } 166 167 override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { 168 if (value !is Boolean) return 169 val tetheringManager = context.tetheringManager ?: return 170 if (value) { 171 tetheringManager.startTethering(TETHERING_WIFI, HandlerExecutor.main, this) 172 } else { 173 tetheringManager.stopTethering(TETHERING_WIFI) 174 } 175 } 176 177 override fun onFirstObserverAdded() { 178 val apManager = WifiTetherSoftApManager(context.wifiManager, this) 179 wifiTetherSoftApManager = apManager 180 apManager.registerSoftApCallback() 181 dataSaverStore.addObserver(DATA_SAVER_KEY, this, HandlerExecutor.main) 182 } 183 184 override fun onLastObserverRemoved() { 185 dataSaverStore.removeObserver(DATA_SAVER_KEY, this) 186 wifiTetherSoftApManager?.unRegisterSoftApCallback() 187 } 188 189 override fun onStateChanged(state: Int, failureReason: Int) { 190 Log.d(TAG, "onStateChanged(),state=$state,failureReason=$failureReason") 191 sapFailureReason = failureReason 192 if (state == WifiManager.WIFI_AP_STATE_DISABLED) sapClientsSize = null 193 notifyChange(KEY, PreferenceChangeReason.VALUE) 194 } 195 196 override fun onConnectedClientsChanged(clients: List<WifiClient>?) { 197 sapClientsSize = clients?.size ?: 0 198 Log.d(TAG, "onConnectedClientsChanged(),sapClientsSize=$sapClientsSize") 199 notifyChange(KEY, PreferenceChangeReason.STATE) 200 } 201 202 override fun onTetheringStarted() {} 203 204 override fun onTetheringFailed(error: Int) { 205 Log.e(TAG, "onTetheringFailed(),error=$error") 206 } 207 208 override fun onKeyChanged(key: String, reason: Int) = notifyChange(KEY, reason) 209 } 210 211 companion object { 212 const val TAG = "WifiHotspotSwitchPreference" 213 const val KEY = "wifi_tether" 214 } 215 } 216 // LINT.ThenChange(WifiTetherPreferenceController.java) 217