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 import static android.view.View.INVISIBLE; 21 import static android.view.View.VISIBLE; 22 23 import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot; 24 25 import android.app.settings.SettingsEnums; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.net.wifi.SoftApConfiguration; 31 import android.os.Bundle; 32 import android.os.UserManager; 33 import android.util.Log; 34 35 import androidx.annotation.Nullable; 36 import androidx.annotation.VisibleForTesting; 37 import androidx.preference.Preference; 38 39 import com.android.settings.R; 40 import com.android.settings.SettingsActivity; 41 import com.android.settings.dashboard.RestrictedDashboardFragment; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settings.search.BaseSearchIndexProvider; 44 import com.android.settings.widget.SettingsMainSwitchBar; 45 import com.android.settings.wifi.WifiUtils; 46 import com.android.settings.wifi.repository.SharedConnectivityRepository; 47 import com.android.settingslib.TetherUtil; 48 import com.android.settingslib.core.AbstractPreferenceController; 49 import com.android.settingslib.search.SearchIndexable; 50 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 55 @SearchIndexable 56 public class WifiTetherSettings extends RestrictedDashboardFragment 57 implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener { 58 59 private static final String TAG = "WifiTetherSettings"; 60 private static final IntentFilter TETHER_STATE_CHANGE_FILTER; 61 private static final String KEY_WIFI_TETHER_SCREEN = "wifi_tether_settings_screen"; 62 63 @VisibleForTesting 64 static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name"; 65 @VisibleForTesting 66 static final String KEY_WIFI_TETHER_SECURITY = "wifi_tether_security"; 67 @VisibleForTesting 68 static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = "wifi_tether_network_password"; 69 @VisibleForTesting 70 static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off"; 71 @VisibleForTesting 72 static final String KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY = 73 WifiTetherMaximizeCompatibilityPreferenceController.PREF_KEY; 74 @VisibleForTesting 75 static final String KEY_WIFI_HOTSPOT_SECURITY = "wifi_hotspot_security"; 76 @VisibleForTesting 77 static final String KEY_WIFI_HOTSPOT_SPEED = "wifi_hotspot_speed"; 78 @VisibleForTesting 79 static final String KEY_INSTANT_HOTSPOT = "wifi_hotspot_instant"; 80 81 @VisibleForTesting 82 SettingsMainSwitchBar mMainSwitchBar; 83 private WifiTetherSwitchBarController mSwitchBarController; 84 @VisibleForTesting 85 WifiTetherSSIDPreferenceController mSSIDPreferenceController; 86 @VisibleForTesting 87 WifiTetherPasswordPreferenceController mPasswordPreferenceController; 88 @VisibleForTesting 89 WifiTetherSecurityPreferenceController mSecurityPreferenceController; 90 @VisibleForTesting 91 WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; 92 @VisibleForTesting 93 WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; 94 95 @VisibleForTesting 96 boolean mUnavailable; 97 private WifiRestriction mWifiRestriction; 98 @VisibleForTesting 99 TetherChangeReceiver mTetherChangeReceiver; 100 101 @VisibleForTesting 102 WifiTetherViewModel mWifiTetherViewModel; 103 @VisibleForTesting 104 Preference mWifiHotspotSecurity; 105 @VisibleForTesting 106 Preference mWifiHotspotSpeed; 107 @VisibleForTesting 108 Preference mInstantHotspot; 109 110 static { 111 TETHER_STATE_CHANGE_FILTER = new IntentFilter(WIFI_AP_STATE_CHANGED_ACTION); 112 } 113 WifiTetherSettings()114 public WifiTetherSettings() { 115 super(UserManager.DISALLOW_CONFIG_TETHERING); 116 mWifiRestriction = new WifiRestriction(); 117 } 118 WifiTetherSettings(WifiRestriction wifiRestriction)119 public WifiTetherSettings(WifiRestriction wifiRestriction) { 120 super(UserManager.DISALLOW_CONFIG_TETHERING); 121 mWifiRestriction = wifiRestriction; 122 } 123 124 @Override getMetricsCategory()125 public int getMetricsCategory() { 126 return SettingsEnums.WIFI_TETHER_SETTINGS; 127 } 128 129 @Override getLogTag()130 protected String getLogTag() { 131 return "WifiTetherSettings"; 132 } 133 134 @Override onCreate(Bundle icicle)135 public void onCreate(Bundle icicle) { 136 super.onCreate(icicle); 137 if (!canShowWifiHotspot(getContext())) { 138 Log.e(TAG, "can not launch Wi-Fi hotspot settings" 139 + " because the config is not set to show."); 140 finish(); 141 return; 142 } 143 144 setIfOnlyAvailableForAdmins(true); 145 mUnavailable = isUiRestricted() || !mWifiRestriction.isHotspotAvailable(getContext()); 146 if (mUnavailable) { 147 return; 148 } 149 150 mWifiTetherViewModel = FeatureFactory.getFeatureFactory().getWifiFeatureProvider() 151 .getWifiTetherViewModel(this); 152 if (mWifiTetherViewModel != null) { 153 setupSpeedFeature(mWifiTetherViewModel.isSpeedFeatureAvailable()); 154 setupInstantHotspot(mWifiTetherViewModel.isInstantHotspotFeatureAvailable()); 155 mWifiTetherViewModel.getRestarting().observe(this, this::onRestartingChanged); 156 } 157 } 158 159 @VisibleForTesting setupSpeedFeature(boolean isSpeedFeatureAvailable)160 void setupSpeedFeature(boolean isSpeedFeatureAvailable) { 161 mWifiHotspotSecurity = findPreference(KEY_WIFI_HOTSPOT_SECURITY); 162 mWifiHotspotSpeed = findPreference(KEY_WIFI_HOTSPOT_SPEED); 163 if (mWifiHotspotSecurity == null || mWifiHotspotSpeed == null) { 164 return; 165 } 166 mWifiHotspotSecurity.setVisible(isSpeedFeatureAvailable); 167 mWifiHotspotSpeed.setVisible(isSpeedFeatureAvailable); 168 if (isSpeedFeatureAvailable) { 169 mWifiTetherViewModel.getSecuritySummary().observe(this, this::onSecuritySummaryChanged); 170 mWifiTetherViewModel.getSpeedSummary().observe(this, this::onSpeedSummaryChanged); 171 } 172 } 173 174 @VisibleForTesting setupInstantHotspot(boolean isFeatureAvailable)175 void setupInstantHotspot(boolean isFeatureAvailable) { 176 if (!isFeatureAvailable) { 177 return; 178 } 179 mInstantHotspot = findPreference(KEY_INSTANT_HOTSPOT); 180 if (mInstantHotspot == null) { 181 Log.e(TAG, "Failed to find Instant Hotspot preference:" + KEY_INSTANT_HOTSPOT); 182 return; 183 } 184 mWifiTetherViewModel.getInstantHotspotSummary() 185 .observe(this, this::onInstantHotspotChanged); 186 mInstantHotspot.setOnPreferenceClickListener(p -> { 187 mWifiTetherViewModel.launchInstantHotspotSettings(); 188 return true; 189 }); 190 } 191 192 @Override onAttach(Context context)193 public void onAttach(Context context) { 194 super.onAttach(context); 195 mTetherChangeReceiver = new TetherChangeReceiver(); 196 197 mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class); 198 mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class); 199 mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class); 200 mMaxCompatibilityPrefController = 201 use(WifiTetherMaximizeCompatibilityPreferenceController.class); 202 mWifiTetherAutoOffPreferenceController = use(WifiTetherAutoOffPreferenceController.class); 203 } 204 205 @Override onActivityCreated(Bundle savedInstanceState)206 public void onActivityCreated(Bundle savedInstanceState) { 207 super.onActivityCreated(savedInstanceState); 208 if (mUnavailable) { 209 return; 210 } 211 // Assume we are in a SettingsActivity. This is only safe because we currently use 212 // SettingsActivity as base for all preference fragments. 213 final SettingsActivity activity = (SettingsActivity) getActivity(); 214 mMainSwitchBar = activity.getSwitchBar(); 215 mMainSwitchBar.setTitle(getString(R.string.use_wifi_hotsopt_main_switch_title)); 216 mSwitchBarController = new WifiTetherSwitchBarController(activity, mMainSwitchBar); 217 getSettingsLifecycle().addObserver(mSwitchBarController); 218 mMainSwitchBar.show(); 219 } 220 221 @Override onStart()222 public void onStart() { 223 super.onStart(); 224 if (!mWifiRestriction.isHotspotAvailable(getContext())) { 225 getEmptyTextView().setText(R.string.not_allowed_by_ent); 226 getPreferenceScreen().removeAll(); 227 return; 228 } 229 if (mUnavailable) { 230 if (!isUiRestrictedByOnlyAdmin()) { 231 getEmptyTextView() 232 .setText(com.android.settingslib.R.string.tethering_settings_not_available); 233 } 234 getPreferenceScreen().removeAll(); 235 return; 236 } 237 final Context context = getContext(); 238 if (context != null) { 239 context.registerReceiver(mTetherChangeReceiver, TETHER_STATE_CHANGE_FILTER, 240 Context.RECEIVER_EXPORTED_UNAUDITED); 241 // The intent WIFI_AP_STATE_CHANGED_ACTION is not sticky intent anymore after SC-V2 242 // Handle the initial state after register the receiver. 243 updateDisplayWithNewConfig(); 244 } 245 mWifiTetherViewModel.refresh(); 246 } 247 248 @Override onStop()249 public void onStop() { 250 super.onStop(); 251 if (mUnavailable) { 252 return; 253 } 254 final Context context = getContext(); 255 if (context != null) { 256 context.unregisterReceiver(mTetherChangeReceiver); 257 } 258 } 259 onSecuritySummaryChanged(Integer securityResId)260 protected void onSecuritySummaryChanged(Integer securityResId) { 261 mWifiHotspotSecurity.setSummary(securityResId); 262 } 263 onSpeedSummaryChanged(Integer summaryResId)264 protected void onSpeedSummaryChanged(Integer summaryResId) { 265 mWifiHotspotSpeed.setSummary(summaryResId); 266 } 267 268 @Override getPreferenceScreenResId()269 protected int getPreferenceScreenResId() { 270 return R.xml.wifi_tether_settings; 271 } 272 273 @Override createPreferenceControllers(Context context)274 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 275 return buildPreferenceControllers(context, this::onTetherConfigUpdated); 276 } 277 buildPreferenceControllers(Context context, WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener)278 private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, 279 WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) { 280 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 281 controllers.add(new WifiTetherSSIDPreferenceController(context, listener)); 282 controllers.add(new WifiTetherSecurityPreferenceController(context, listener)); 283 controllers.add(new WifiTetherPasswordPreferenceController(context, listener)); 284 controllers.add( 285 new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF)); 286 controllers.add(new WifiTetherMaximizeCompatibilityPreferenceController(context, listener)); 287 return controllers; 288 } 289 290 @Override onTetherConfigUpdated(AbstractPreferenceController context)291 public void onTetherConfigUpdated(AbstractPreferenceController context) { 292 final SoftApConfiguration config = buildNewConfig(); 293 mPasswordPreferenceController.setSecurityType(config.getSecurityType()); 294 295 mWifiTetherViewModel.setSoftApConfiguration(config); 296 } 297 298 @VisibleForTesting onRestartingChanged(Boolean restarting)299 void onRestartingChanged(Boolean restarting) { 300 mMainSwitchBar.setVisibility((restarting) ? INVISIBLE : VISIBLE); 301 setLoading(restarting, false); 302 } 303 304 @VisibleForTesting onInstantHotspotChanged(String summary)305 void onInstantHotspotChanged(String summary) { 306 if (summary == null) { 307 mInstantHotspot.setVisible(false); 308 return; 309 } 310 mInstantHotspot.setVisible(true); 311 mInstantHotspot.setSummary(summary); 312 } 313 314 @VisibleForTesting buildNewConfig()315 SoftApConfiguration buildNewConfig() { 316 SoftApConfiguration currentConfig = mWifiTetherViewModel.getSoftApConfiguration(); 317 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(currentConfig); 318 configBuilder.setSsid(mSSIDPreferenceController.getSSID()); 319 int securityType = 320 mWifiTetherViewModel.isSpeedFeatureAvailable() 321 ? currentConfig.getSecurityType() 322 : mSecurityPreferenceController.getSecurityType(); 323 String passphrase = 324 securityType == SoftApConfiguration.SECURITY_TYPE_OPEN 325 ? null 326 : mPasswordPreferenceController.getPasswordValidated(securityType); 327 configBuilder.setPassphrase(passphrase, securityType); 328 if (!mWifiTetherViewModel.isSpeedFeatureAvailable()) { 329 mMaxCompatibilityPrefController.setupMaximizeCompatibility(configBuilder); 330 } 331 configBuilder.setAutoShutdownEnabled( 332 mWifiTetherAutoOffPreferenceController.isEnabled()); 333 return configBuilder.build(); 334 } 335 updateDisplayWithNewConfig()336 private void updateDisplayWithNewConfig() { 337 use(WifiTetherSSIDPreferenceController.class).updateDisplay(); 338 use(WifiTetherSecurityPreferenceController.class).updateDisplay(); 339 use(WifiTetherPasswordPreferenceController.class).updateDisplay(); 340 use(WifiTetherMaximizeCompatibilityPreferenceController.class).updateDisplay(); 341 } 342 343 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 344 new SearchIndexProvider(R.xml.wifi_tether_settings); 345 346 @VisibleForTesting 347 static class SearchIndexProvider extends BaseSearchIndexProvider { 348 349 private final WifiRestriction mWifiRestriction; 350 private final boolean mIsInstantHotspotEnabled; 351 SearchIndexProvider(int xmlRes)352 SearchIndexProvider(int xmlRes) { 353 super(xmlRes); 354 mWifiRestriction = new WifiRestriction(); 355 mIsInstantHotspotEnabled = SharedConnectivityRepository.isDeviceConfigEnabled(); 356 } 357 358 @VisibleForTesting SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction, boolean isInstantHotspotEnabled)359 SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction, 360 boolean isInstantHotspotEnabled) { 361 super(xmlRes); 362 mWifiRestriction = wifiRestriction; 363 mIsInstantHotspotEnabled = isInstantHotspotEnabled; 364 } 365 366 @Override getNonIndexableKeys(Context context)367 public List<String> getNonIndexableKeys(Context context) { 368 final List<String> keys = super.getNonIndexableKeys(context); 369 370 if (!mWifiRestriction.isTetherAvailable(context) 371 || !mWifiRestriction.isHotspotAvailable(context)) { 372 keys.add(KEY_WIFI_TETHER_NETWORK_NAME); 373 keys.add(KEY_WIFI_TETHER_SECURITY); 374 keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); 375 keys.add(KEY_WIFI_TETHER_AUTO_OFF); 376 keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); 377 keys.add(KEY_INSTANT_HOTSPOT); 378 } else if (!mIsInstantHotspotEnabled) { 379 keys.add(KEY_INSTANT_HOTSPOT); 380 } 381 382 // Remove duplicate 383 keys.add(KEY_WIFI_TETHER_SCREEN); 384 return keys; 385 } 386 387 @Override isPageSearchEnabled(Context context)388 protected boolean isPageSearchEnabled(Context context) { 389 if (context == null) { 390 return false; 391 } 392 UserManager userManager = context.getSystemService(UserManager.class); 393 if (userManager == null || !userManager.isAdminUser()) { 394 return false; 395 } 396 return WifiUtils.canShowWifiHotspot(context); 397 } 398 399 @Override createPreferenceControllers(Context context)400 public List<AbstractPreferenceController> createPreferenceControllers(Context context) { 401 return buildPreferenceControllers(context, null /* listener */); 402 } 403 } 404 405 @VisibleForTesting 406 static class WifiRestriction { isTetherAvailable(@ullable Context context)407 public boolean isTetherAvailable(@Nullable Context context) { 408 if (context == null) return true; 409 return TetherUtil.isTetherAvailable(context); 410 } 411 isHotspotAvailable(@ullable Context context)412 public boolean isHotspotAvailable(@Nullable Context context) { 413 if (context == null) return true; 414 return WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context); 415 } 416 } 417 418 @VisibleForTesting 419 class TetherChangeReceiver extends BroadcastReceiver { 420 @Override onReceive(Context content, Intent intent)421 public void onReceive(Context content, Intent intent) { 422 String action = intent.getAction(); 423 Log.d(TAG, "updating display config due to receiving broadcast action " + action); 424 updateDisplayWithNewConfig(); 425 } 426 } 427 } 428