1 /* 2 * Copyright (C) 2017 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 static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION; 20 21 import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot; 22 23 import android.app.settings.SettingsEnums; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.net.wifi.SoftApConfiguration; 29 import android.net.wifi.WifiManager; 30 import android.os.Bundle; 31 import android.os.UserManager; 32 import android.util.FeatureFlagUtils; 33 import android.util.Log; 34 35 import androidx.annotation.Nullable; 36 import androidx.annotation.VisibleForTesting; 37 38 import com.android.settings.R; 39 import com.android.settings.SettingsActivity; 40 import com.android.settings.core.FeatureFlags; 41 import com.android.settings.dashboard.RestrictedDashboardFragment; 42 import com.android.settings.search.BaseSearchIndexProvider; 43 import com.android.settings.widget.SettingsMainSwitchBar; 44 import com.android.settings.wifi.WifiUtils; 45 import com.android.settingslib.TetherUtil; 46 import com.android.settingslib.core.AbstractPreferenceController; 47 import com.android.settingslib.search.SearchIndexable; 48 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 53 @SearchIndexable 54 public class WifiTetherSettings extends RestrictedDashboardFragment 55 implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener { 56 57 private static final String TAG = "WifiTetherSettings"; 58 private static final IntentFilter TETHER_STATE_CHANGE_FILTER; 59 private static final String KEY_WIFI_TETHER_SCREEN = "wifi_tether_settings_screen"; 60 61 @VisibleForTesting 62 static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name"; 63 @VisibleForTesting 64 static final String KEY_WIFI_TETHER_SECURITY = "wifi_tether_security"; 65 @VisibleForTesting 66 static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = "wifi_tether_network_password"; 67 @VisibleForTesting 68 static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off"; 69 @VisibleForTesting 70 static final String KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY = 71 WifiTetherMaximizeCompatibilityPreferenceController.PREF_KEY; 72 73 private WifiTetherSwitchBarController mSwitchBarController; 74 private WifiTetherSSIDPreferenceController mSSIDPreferenceController; 75 private WifiTetherPasswordPreferenceController mPasswordPreferenceController; 76 private WifiTetherSecurityPreferenceController mSecurityPreferenceController; 77 private WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; 78 79 private WifiManager mWifiManager; 80 private boolean mRestartWifiApAfterConfigChange; 81 private boolean mUnavailable; 82 private WifiRestriction mWifiRestriction; 83 @VisibleForTesting 84 TetherChangeReceiver mTetherChangeReceiver; 85 86 static { 87 TETHER_STATE_CHANGE_FILTER = new IntentFilter(WIFI_AP_STATE_CHANGED_ACTION); 88 } 89 WifiTetherSettings()90 public WifiTetherSettings() { 91 super(UserManager.DISALLOW_CONFIG_TETHERING); 92 mWifiRestriction = new WifiRestriction(); 93 } 94 WifiTetherSettings(WifiRestriction wifiRestriction)95 public WifiTetherSettings(WifiRestriction wifiRestriction) { 96 super(UserManager.DISALLOW_CONFIG_TETHERING); 97 mWifiRestriction = wifiRestriction; 98 } 99 100 @Override getMetricsCategory()101 public int getMetricsCategory() { 102 return SettingsEnums.WIFI_TETHER_SETTINGS; 103 } 104 105 @Override getLogTag()106 protected String getLogTag() { 107 return "WifiTetherSettings"; 108 } 109 110 @Override onCreate(Bundle icicle)111 public void onCreate(Bundle icicle) { 112 super.onCreate(icicle); 113 if (!canShowWifiHotspot(getContext())) { 114 Log.e(TAG, "can not launch Wi-Fi hotspot settings" 115 + " because the config is not set to show."); 116 finish(); 117 return; 118 } 119 120 setIfOnlyAvailableForAdmins(true); 121 mUnavailable = isUiRestricted() || !mWifiRestriction.isHotspotAvailable(getContext()); 122 } 123 124 @Override onAttach(Context context)125 public void onAttach(Context context) { 126 super.onAttach(context); 127 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 128 mTetherChangeReceiver = new TetherChangeReceiver(); 129 130 mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class); 131 mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class); 132 mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class); 133 mMaxCompatibilityPrefController = 134 use(WifiTetherMaximizeCompatibilityPreferenceController.class); 135 } 136 137 @Override onActivityCreated(Bundle savedInstanceState)138 public void onActivityCreated(Bundle savedInstanceState) { 139 super.onActivityCreated(savedInstanceState); 140 if (mUnavailable) { 141 return; 142 } 143 // Assume we are in a SettingsActivity. This is only safe because we currently use 144 // SettingsActivity as base for all preference fragments. 145 final SettingsActivity activity = (SettingsActivity) getActivity(); 146 final SettingsMainSwitchBar switchBar = activity.getSwitchBar(); 147 switchBar.setTitle(getContext().getString(R.string.use_wifi_hotsopt_main_switch_title)); 148 mSwitchBarController = new WifiTetherSwitchBarController(activity, switchBar); 149 getSettingsLifecycle().addObserver(mSwitchBarController); 150 switchBar.show(); 151 } 152 153 @Override onStart()154 public void onStart() { 155 super.onStart(); 156 if (!mWifiRestriction.isHotspotAvailable(getContext())) { 157 getEmptyTextView().setText(R.string.not_allowed_by_ent); 158 getPreferenceScreen().removeAll(); 159 return; 160 } 161 if (mUnavailable) { 162 if (!isUiRestrictedByOnlyAdmin()) { 163 getEmptyTextView().setText(R.string.tethering_settings_not_available); 164 } 165 getPreferenceScreen().removeAll(); 166 return; 167 } 168 final Context context = getContext(); 169 if (context != null) { 170 context.registerReceiver(mTetherChangeReceiver, TETHER_STATE_CHANGE_FILTER, 171 Context.RECEIVER_EXPORTED_UNAUDITED); 172 // The intent WIFI_AP_STATE_CHANGED_ACTION is not sticky intent anymore after SC-V2 173 // Handle the initial state after register the receiver. 174 updateDisplayWithNewConfig(); 175 } 176 } 177 178 @Override onStop()179 public void onStop() { 180 super.onStop(); 181 if (mUnavailable) { 182 return; 183 } 184 final Context context = getContext(); 185 if (context != null) { 186 context.unregisterReceiver(mTetherChangeReceiver); 187 } 188 } 189 190 191 @Override getPreferenceScreenResId()192 protected int getPreferenceScreenResId() { 193 return R.xml.wifi_tether_settings; 194 } 195 196 @Override createPreferenceControllers(Context context)197 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 198 return buildPreferenceControllers(context, this::onTetherConfigUpdated); 199 } 200 buildPreferenceControllers(Context context, WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener)201 private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, 202 WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) { 203 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 204 controllers.add(new WifiTetherSSIDPreferenceController(context, listener)); 205 controllers.add(new WifiTetherSecurityPreferenceController(context, listener)); 206 controllers.add(new WifiTetherPasswordPreferenceController(context, listener)); 207 controllers.add( 208 new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF)); 209 controllers.add(new WifiTetherMaximizeCompatibilityPreferenceController(context, listener)); 210 return controllers; 211 } 212 213 @Override onTetherConfigUpdated(AbstractPreferenceController context)214 public void onTetherConfigUpdated(AbstractPreferenceController context) { 215 final SoftApConfiguration config = buildNewConfig(); 216 mPasswordPreferenceController.setSecurityType(config.getSecurityType()); 217 218 /** 219 * if soft AP is stopped, bring up 220 * else restart with new config 221 * TODO: update config on a running access point when framework support is added 222 */ 223 if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) { 224 Log.d("TetheringSettings", 225 "Wifi AP config changed while enabled, stop and restart"); 226 mRestartWifiApAfterConfigChange = true; 227 mSwitchBarController.stopTether(); 228 } 229 mWifiManager.setSoftApConfiguration(config); 230 } 231 buildNewConfig()232 private SoftApConfiguration buildNewConfig() { 233 final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 234 final int securityType = mSecurityPreferenceController.getSecurityType(); 235 configBuilder.setSsid(mSSIDPreferenceController.getSSID()); 236 if (securityType != SoftApConfiguration.SECURITY_TYPE_OPEN) { 237 configBuilder.setPassphrase( 238 mPasswordPreferenceController.getPasswordValidated(securityType), 239 securityType); 240 } 241 mMaxCompatibilityPrefController.setupMaximizeCompatibility(configBuilder); 242 return configBuilder.build(); 243 } 244 startTether()245 private void startTether() { 246 mRestartWifiApAfterConfigChange = false; 247 mSwitchBarController.startTether(); 248 } 249 updateDisplayWithNewConfig()250 private void updateDisplayWithNewConfig() { 251 use(WifiTetherSSIDPreferenceController.class).updateDisplay(); 252 use(WifiTetherSecurityPreferenceController.class).updateDisplay(); 253 use(WifiTetherPasswordPreferenceController.class).updateDisplay(); 254 use(WifiTetherMaximizeCompatibilityPreferenceController.class).updateDisplay(); 255 } 256 257 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 258 new SearchIndexProvider(R.xml.wifi_tether_settings); 259 260 @VisibleForTesting 261 static class SearchIndexProvider extends BaseSearchIndexProvider { 262 263 private final WifiRestriction mWifiRestriction; 264 SearchIndexProvider(int xmlRes)265 SearchIndexProvider(int xmlRes) { 266 super(xmlRes); 267 mWifiRestriction = new WifiRestriction(); 268 } 269 270 @VisibleForTesting SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction)271 SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction) { 272 super(xmlRes); 273 mWifiRestriction = wifiRestriction; 274 } 275 276 @Override getNonIndexableKeys(Context context)277 public List<String> getNonIndexableKeys(Context context) { 278 final List<String> keys = super.getNonIndexableKeys(context); 279 280 if (!mWifiRestriction.isTetherAvailable(context) 281 || !mWifiRestriction.isHotspotAvailable(context)) { 282 keys.add(KEY_WIFI_TETHER_NETWORK_NAME); 283 keys.add(KEY_WIFI_TETHER_SECURITY); 284 keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); 285 keys.add(KEY_WIFI_TETHER_AUTO_OFF); 286 keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); 287 } 288 289 // Remove duplicate 290 keys.add(KEY_WIFI_TETHER_SCREEN); 291 return keys; 292 } 293 294 @Override isPageSearchEnabled(Context context)295 protected boolean isPageSearchEnabled(Context context) { 296 if (context == null || !WifiUtils.canShowWifiHotspot(context)) return false; 297 return !FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE); 298 } 299 300 @Override createPreferenceControllers(Context context)301 public List<AbstractPreferenceController> createPreferenceControllers(Context context) { 302 return buildPreferenceControllers(context, null /* listener */); 303 } 304 } 305 306 @VisibleForTesting 307 static class WifiRestriction { isTetherAvailable(@ullable Context context)308 public boolean isTetherAvailable(@Nullable Context context) { 309 if (context == null) return true; 310 return TetherUtil.isTetherAvailable(context); 311 } 312 isHotspotAvailable(@ullable Context context)313 public boolean isHotspotAvailable(@Nullable Context context) { 314 if (context == null) return true; 315 return WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context); 316 } 317 } 318 319 @VisibleForTesting 320 class TetherChangeReceiver extends BroadcastReceiver { 321 @Override onReceive(Context content, Intent intent)322 public void onReceive(Context content, Intent intent) { 323 String action = intent.getAction(); 324 Log.d(TAG, "updating display config due to receiving broadcast action " + action); 325 updateDisplayWithNewConfig(); 326 if (action.equals(WIFI_AP_STATE_CHANGED_ACTION)) { 327 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0); 328 if (state == WifiManager.WIFI_AP_STATE_DISABLED 329 && mRestartWifiApAfterConfigChange) { 330 startTether(); 331 } 332 } 333 } 334 } 335 } 336