WifiSettings.java revision d9e2c946af8cebeaf9fa81ca5657d3932ee56d1b
1/* 2 * Copyright (C) 2010 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 17package com.android.settings.wifi; 18 19import android.app.Activity; 20import android.app.Dialog; 21import android.app.admin.DevicePolicyManager; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.res.Resources; 31import android.net.ConnectivityManager; 32import android.net.NetworkInfo; 33import android.net.NetworkInfo.State; 34import android.net.wifi.WifiConfiguration; 35import android.net.wifi.WifiManager; 36import android.net.wifi.WpsInfo; 37import android.nfc.NfcAdapter; 38import android.os.Bundle; 39import android.os.HandlerThread; 40import android.os.Process; 41import android.provider.Settings; 42import android.support.v7.preference.Preference; 43import android.support.v7.preference.PreferenceManager; 44import android.support.v7.preference.PreferenceViewHolder; 45import android.text.Spannable; 46import android.text.TextUtils; 47import android.text.style.TextAppearanceSpan; 48import android.util.Log; 49import android.view.ContextMenu; 50import android.view.ContextMenu.ContextMenuInfo; 51import android.view.Menu; 52import android.view.MenuInflater; 53import android.view.MenuItem; 54import android.view.View; 55import android.widget.ProgressBar; 56import android.widget.TextView; 57import android.widget.TextView.BufferType; 58import android.widget.Toast; 59 60import com.android.internal.logging.MetricsProto.MetricsEvent; 61import com.android.settings.LinkifyUtils; 62import com.android.settings.R; 63import com.android.settings.RestrictedSettingsFragment; 64import com.android.settings.SettingsActivity; 65import com.android.settings.dashboard.SummaryLoader; 66import com.android.settings.location.ScanningSettings; 67import com.android.settings.search.BaseSearchIndexProvider; 68import com.android.settings.search.Indexable; 69import com.android.settings.search.SearchIndexableRaw; 70import com.android.settingslib.RestrictedLockUtils; 71import com.android.settingslib.wifi.AccessPoint; 72import com.android.settingslib.wifi.AccessPoint.AccessPointListener; 73import com.android.settingslib.wifi.AccessPointPreference; 74import com.android.settingslib.wifi.WifiStatusTracker; 75import com.android.settingslib.wifi.WifiTracker; 76 77import java.util.ArrayList; 78import java.util.Collection; 79import java.util.List; 80 81import static android.os.UserManager.DISALLOW_CONFIG_WIFI; 82 83/** 84 * Two types of UI are provided here. 85 * 86 * The first is for "usual Settings", appearing as any other Setup fragment. 87 * 88 * The second is for Setup Wizard, with a simplified interface that hides the action bar 89 * and menus. 90 */ 91public class WifiSettings extends RestrictedSettingsFragment 92 implements Indexable, WifiTracker.WifiListener, AccessPointListener, 93 WifiDialog.WifiDialogListener { 94 95 private static final String TAG = "WifiSettings"; 96 97 /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST; 98 private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1; 99 private static final int MENU_ID_ADVANCED = Menu.FIRST + 4; 100 private static final int MENU_ID_SCAN = Menu.FIRST + 5; 101 private static final int MENU_ID_CONNECT = Menu.FIRST + 6; 102 private static final int MENU_ID_FORGET = Menu.FIRST + 7; 103 private static final int MENU_ID_MODIFY = Menu.FIRST + 8; 104 private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9; 105 private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10; 106 107 public static final int WIFI_DIALOG_ID = 1; 108 /* package */ static final int WPS_PBC_DIALOG_ID = 2; 109 private static final int WPS_PIN_DIALOG_ID = 3; 110 private static final int WRITE_NFC_DIALOG_ID = 6; 111 112 // Instance state keys 113 private static final String SAVE_DIALOG_MODE = "dialog_mode"; 114 private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; 115 private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state"; 116 117 private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list"; 118 119 protected WifiManager mWifiManager; 120 private WifiManager.ActionListener mConnectListener; 121 private WifiManager.ActionListener mSaveListener; 122 private WifiManager.ActionListener mForgetListener; 123 124 private WifiEnabler mWifiEnabler; 125 // An access point being editted is stored here. 126 private AccessPoint mSelectedAccessPoint; 127 128 private WifiDialog mDialog; 129 private WriteWifiConfigToNfcDialog mWifiToNfcDialog; 130 131 private ProgressBar mProgressHeader; 132 133 // this boolean extra specifies whether to disable the Next button when not connected. Used by 134 // account creation outside of setup wizard. 135 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; 136 // This string extra specifies a network to open the connect dialog on, so the user can enter 137 // network credentials. This is used by quick settings for secured networks. 138 private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid"; 139 140 // should Next button only be enabled when we have a connection? 141 private boolean mEnableNextOnConnection; 142 143 // Save the dialog details 144 private int mDialogMode; 145 private AccessPoint mDlgAccessPoint; 146 private Bundle mAccessPointSavedState; 147 private Bundle mWifiNfcDialogSavedState; 148 149 private WifiTracker mWifiTracker; 150 private String mOpenSsid; 151 152 private HandlerThread mBgThread; 153 154 private AccessPointPreference.UserBadgeCache mUserBadgeCache; 155 private Preference mAddPreference; 156 157 private MenuItem mScanMenuItem; 158 159 /* End of "used in Wifi Setup context" */ 160 161 public WifiSettings() { 162 super(DISALLOW_CONFIG_WIFI); 163 } 164 165 @Override 166 public void onViewCreated(View view, Bundle savedInstanceState) { 167 super.onViewCreated(view, savedInstanceState); 168 final Activity activity = getActivity(); 169 if (activity != null) { 170 mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header); 171 } 172 } 173 174 @Override 175 public void onCreate(Bundle icicle) { 176 super.onCreate(icicle); 177 getPreferenceManager().setPreferenceComparisonCallback( 178 new PreferenceManager.SimplePreferenceComparisonCallback()); 179 addPreferencesFromResource(R.xml.wifi_settings); 180 mAddPreference = new Preference(getContext()); 181 mAddPreference.setIcon(R.drawable.ic_menu_add_inset); 182 mAddPreference.setTitle(R.string.wifi_add_network); 183 184 mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager()); 185 186 mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 187 mBgThread.start(); 188 } 189 190 @Override 191 public void onDestroy() { 192 mBgThread.quit(); 193 super.onDestroy(); 194 } 195 196 @Override 197 public void onActivityCreated(Bundle savedInstanceState) { 198 super.onActivityCreated(savedInstanceState); 199 200 mWifiTracker = 201 new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false); 202 mWifiManager = mWifiTracker.getManager(); 203 204 mConnectListener = new WifiManager.ActionListener() { 205 @Override 206 public void onSuccess() { 207 } 208 @Override 209 public void onFailure(int reason) { 210 Activity activity = getActivity(); 211 if (activity != null) { 212 Toast.makeText(activity, 213 R.string.wifi_failed_connect_message, 214 Toast.LENGTH_SHORT).show(); 215 } 216 } 217 }; 218 219 mSaveListener = new WifiManager.ActionListener() { 220 @Override 221 public void onSuccess() { 222 } 223 @Override 224 public void onFailure(int reason) { 225 Activity activity = getActivity(); 226 if (activity != null) { 227 Toast.makeText(activity, 228 R.string.wifi_failed_save_message, 229 Toast.LENGTH_SHORT).show(); 230 } 231 } 232 }; 233 234 mForgetListener = new WifiManager.ActionListener() { 235 @Override 236 public void onSuccess() { 237 } 238 @Override 239 public void onFailure(int reason) { 240 Activity activity = getActivity(); 241 if (activity != null) { 242 Toast.makeText(activity, 243 R.string.wifi_failed_forget_message, 244 Toast.LENGTH_SHORT).show(); 245 } 246 } 247 }; 248 249 if (savedInstanceState != null) { 250 mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE); 251 if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { 252 mAccessPointSavedState = 253 savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); 254 } 255 256 if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) { 257 mWifiNfcDialogSavedState = 258 savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE); 259 } 260 } 261 262 // if we're supposed to enable/disable the Next button based on our current connection 263 // state, start it off in the right state 264 Intent intent = getActivity().getIntent(); 265 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); 266 267 if (mEnableNextOnConnection) { 268 if (hasNextButton()) { 269 final ConnectivityManager connectivity = (ConnectivityManager) 270 getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); 271 if (connectivity != null) { 272 NetworkInfo info = connectivity.getNetworkInfo( 273 ConnectivityManager.TYPE_WIFI); 274 changeNextButtonState(info.isConnected()); 275 } 276 } 277 } 278 279 registerForContextMenu(getListView()); 280 setHasOptionsMenu(true); 281 282 if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { 283 mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); 284 onAccessPointsChanged(); 285 } 286 } 287 288 @Override 289 public void onDestroyView() { 290 super.onDestroyView(); 291 292 if (mWifiEnabler != null) { 293 mWifiEnabler.teardownSwitchBar(); 294 } 295 } 296 297 @Override 298 public void onStart() { 299 super.onStart(); 300 301 // On/off switch is hidden for Setup Wizard (returns null) 302 mWifiEnabler = createWifiEnabler(); 303 } 304 305 /** 306 * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard) 307 */ 308 /* package */ WifiEnabler createWifiEnabler() { 309 final SettingsActivity activity = (SettingsActivity) getActivity(); 310 return new WifiEnabler(activity, activity.getSwitchBar(), mMetricsFeatureProvider); 311 } 312 313 @Override 314 public void onResume() { 315 final Activity activity = getActivity(); 316 super.onResume(); 317 removePreference("dummy"); 318 if (mWifiEnabler != null) { 319 mWifiEnabler.resume(activity); 320 } 321 322 mWifiTracker.startTracking(); 323 activity.invalidateOptionsMenu(); 324 } 325 326 @Override 327 public void onPause() { 328 super.onPause(); 329 if (mWifiEnabler != null) { 330 mWifiEnabler.pause(); 331 } 332 333 mWifiTracker.stopTracking(); 334 } 335 336 @Override 337 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 338 // If the user is not allowed to configure wifi, do not show the menu. 339 if (isUiRestricted()) return; 340 341 addOptionsMenuItems(menu); 342 super.onCreateOptionsMenu(menu, inflater); 343 } 344 345 /** 346 * @param menu 347 */ 348 void addOptionsMenuItems(Menu menu) { 349 final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled(); 350 mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh); 351 mScanMenuItem.setEnabled(wifiIsEnabled) 352 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 353 menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) 354 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 355 menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure) 356 .setIcon(R.drawable.ic_settings_24dp) 357 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 358 } 359 360 @Override 361 public int getMetricsCategory() { 362 return MetricsEvent.WIFI; 363 } 364 365 @Override 366 public void onSaveInstanceState(Bundle outState) { 367 super.onSaveInstanceState(outState); 368 369 // If the dialog is showing, save its state. 370 if (mDialog != null && mDialog.isShowing()) { 371 outState.putInt(SAVE_DIALOG_MODE, mDialogMode); 372 if (mDlgAccessPoint != null) { 373 mAccessPointSavedState = new Bundle(); 374 mDlgAccessPoint.saveWifiState(mAccessPointSavedState); 375 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState); 376 } 377 } 378 379 if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) { 380 Bundle savedState = new Bundle(); 381 mWifiToNfcDialog.saveState(savedState); 382 outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState); 383 } 384 } 385 386 @Override 387 public boolean onOptionsItemSelected(MenuItem item) { 388 // If the user is not allowed to configure wifi, do not handle menu selections. 389 if (isUiRestricted()) return false; 390 391 switch (item.getItemId()) { 392 case MENU_ID_WPS_PBC: 393 showDialog(WPS_PBC_DIALOG_ID); 394 return true; 395 /* 396 case MENU_ID_P2P: 397 if (getActivity() instanceof SettingsActivity) { 398 ((SettingsActivity) getActivity()).startPreferencePanel( 399 WifiP2pSettings.class.getCanonicalName(), 400 null, 401 R.string.wifi_p2p_settings_title, null, 402 this, 0); 403 } else { 404 startFragment(this, WifiP2pSettings.class.getCanonicalName(), 405 R.string.wifi_p2p_settings_title, -1, null); 406 } 407 return true; 408 */ 409 case MENU_ID_WPS_PIN: 410 showDialog(WPS_PIN_DIALOG_ID); 411 return true; 412 case MENU_ID_SCAN: 413 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN); 414 mWifiTracker.forceScan(); 415 return true; 416 case MENU_ID_ADVANCED: 417 if (getActivity() instanceof SettingsActivity) { 418 ((SettingsActivity) getActivity()).startPreferencePanel( 419 AdvancedWifiSettings.class.getCanonicalName(), null, 420 R.string.wifi_advanced_titlebar, null, this, 0); 421 } else { 422 startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), 423 R.string.wifi_advanced_titlebar, -1 /* Do not request a results */, 424 null); 425 } 426 return true; 427 case MENU_ID_CONFIGURE: 428 if (getActivity() instanceof SettingsActivity) { 429 ((SettingsActivity) getActivity()).startPreferencePanel( 430 ConfigureWifiSettings.class.getCanonicalName(), null, 431 R.string.wifi_configure_titlebar, null, this, 0); 432 } else { 433 startFragment(this, ConfigureWifiSettings.class.getCanonicalName(), 434 R.string.wifi_configure_titlebar, -1 /* Do not request a results */, 435 null); 436 } 437 return true; 438 439 } 440 return super.onOptionsItemSelected(item); 441 } 442 443 @Override 444 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { 445 Preference preference = (Preference) view.getTag(); 446 447 if (preference instanceof LongPressAccessPointPreference) { 448 mSelectedAccessPoint = 449 ((LongPressAccessPointPreference) preference).getAccessPoint(); 450 menu.setHeaderTitle(mSelectedAccessPoint.getSsid()); 451 if (mSelectedAccessPoint.isConnectable()) { 452 menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); 453 } 454 455 WifiConfiguration config = mSelectedAccessPoint.getConfig(); 456 // Some configs are ineditable 457 if (isEditabilityLockedDown(getActivity(), config)) { 458 return; 459 } 460 461 if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) { 462 // Allow forgetting a network if either the network is saved or ephemerally 463 // connected. (In the latter case, "forget" blacklists the network so it won't 464 // be used again, ephemerally). 465 menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); 466 } 467 if (mSelectedAccessPoint.isSaved()) { 468 menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); 469 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); 470 if (nfcAdapter != null && nfcAdapter.isEnabled() && 471 mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { 472 // Only allow writing of NFC tags for password-protected networks. 473 menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc); 474 } 475 } 476 } 477 } 478 479 @Override 480 public boolean onContextItemSelected(MenuItem item) { 481 if (mSelectedAccessPoint == null) { 482 return super.onContextItemSelected(item); 483 } 484 switch (item.getItemId()) { 485 case MENU_ID_CONNECT: { 486 boolean isSavedNetwork = mSelectedAccessPoint.isSaved(); 487 if (isSavedNetwork) { 488 connect(mSelectedAccessPoint.getConfig(), isSavedNetwork); 489 } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) { 490 /** Bypass dialog for unsecured networks */ 491 mSelectedAccessPoint.generateOpenNetworkConfig(); 492 connect(mSelectedAccessPoint.getConfig(), isSavedNetwork); 493 } else { 494 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT); 495 } 496 return true; 497 } 498 case MENU_ID_FORGET: { 499 forget(); 500 return true; 501 } 502 case MENU_ID_MODIFY: { 503 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY); 504 return true; 505 } 506 case MENU_ID_WRITE_NFC: 507 showDialog(WRITE_NFC_DIALOG_ID); 508 return true; 509 510 } 511 return super.onContextItemSelected(item); 512 } 513 514 @Override 515 public boolean onPreferenceTreeClick(Preference preference) { 516 if (preference instanceof LongPressAccessPointPreference) { 517 mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint(); 518 if (mSelectedAccessPoint == null) { 519 return false; 520 } 521 /** Bypass dialog for unsecured, unsaved, and inactive networks */ 522 if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE && 523 !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) { 524 mSelectedAccessPoint.generateOpenNetworkConfig(); 525 connect(mSelectedAccessPoint.getConfig(), false /* isSavedNetwork */); 526 } else if (mSelectedAccessPoint.isSaved()) { 527 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW); 528 } else { 529 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT); 530 } 531 } else if (preference == mAddPreference) { 532 onAddNetworkPressed(); 533 } else { 534 return super.onPreferenceTreeClick(preference); 535 } 536 return true; 537 } 538 539 private void showDialog(AccessPoint accessPoint, int dialogMode) { 540 if (accessPoint != null) { 541 WifiConfiguration config = accessPoint.getConfig(); 542 if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) { 543 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), 544 RestrictedLockUtils.getDeviceOwner(getActivity())); 545 return; 546 } 547 } 548 549 if (mDialog != null) { 550 removeDialog(WIFI_DIALOG_ID); 551 mDialog = null; 552 } 553 554 // Save the access point and edit mode 555 mDlgAccessPoint = accessPoint; 556 mDialogMode = dialogMode; 557 558 showDialog(WIFI_DIALOG_ID); 559 } 560 561 @Override 562 public Dialog onCreateDialog(int dialogId) { 563 switch (dialogId) { 564 case WIFI_DIALOG_ID: 565 AccessPoint ap = mDlgAccessPoint; // For manual launch 566 if (ap == null) { // For re-launch from saved state 567 if (mAccessPointSavedState != null) { 568 ap = new AccessPoint(getActivity(), mAccessPointSavedState); 569 // For repeated orientation changes 570 mDlgAccessPoint = ap; 571 // Reset the saved access point data 572 mAccessPointSavedState = null; 573 } 574 } 575 // If it's null, fine, it's for Add Network 576 mSelectedAccessPoint = ap; 577 mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode, 578 /* no hide submit/connect */ false); 579 return mDialog; 580 case WPS_PBC_DIALOG_ID: 581 return new WpsDialog(getActivity(), WpsInfo.PBC); 582 case WPS_PIN_DIALOG_ID: 583 return new WpsDialog(getActivity(), WpsInfo.DISPLAY); 584 case WRITE_NFC_DIALOG_ID: 585 if (mSelectedAccessPoint != null) { 586 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog( 587 getActivity(), mSelectedAccessPoint.getConfig().networkId, 588 mSelectedAccessPoint.getSecurity(), 589 mWifiManager); 590 } else if (mWifiNfcDialogSavedState != null) { 591 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog( 592 getActivity(), mWifiNfcDialogSavedState, mWifiManager); 593 } 594 595 return mWifiToNfcDialog; 596 } 597 return super.onCreateDialog(dialogId); 598 } 599 600 @Override 601 public int getDialogMetricsCategory(int dialogId) { 602 switch (dialogId) { 603 case WIFI_DIALOG_ID: 604 return MetricsEvent.DIALOG_WIFI_AP_EDIT; 605 case WPS_PBC_DIALOG_ID: 606 return MetricsEvent.DIALOG_WIFI_PBC; 607 case WPS_PIN_DIALOG_ID: 608 return MetricsEvent.DIALOG_WIFI_PIN; 609 case WRITE_NFC_DIALOG_ID: 610 return MetricsEvent.DIALOG_WIFI_WRITE_NFC; 611 default: 612 return 0; 613 } 614 } 615 616 /** 617 * Shows the latest access points available with supplemental information like 618 * the strength of network and the security for it. 619 */ 620 @Override 621 public void onAccessPointsChanged() { 622 // Safeguard from some delayed event handling 623 if (getActivity() == null) return; 624 if (isUiRestricted()) { 625 if (!isUiRestrictedByOnlyAdmin()) { 626 addMessagePreference(R.string.wifi_empty_list_user_restricted); 627 } 628 getPreferenceScreen().removeAll(); 629 return; 630 } 631 final int wifiState = mWifiManager.getWifiState(); 632 633 switch (wifiState) { 634 case WifiManager.WIFI_STATE_ENABLED: 635 // AccessPoints are automatically sorted with TreeSet. 636 final Collection<AccessPoint> accessPoints = 637 mWifiTracker.getAccessPoints(); 638 639 boolean hasAvailableAccessPoints = false; 640 int index = 0; 641 cacheRemoveAllPrefs(getPreferenceScreen()); 642 for (AccessPoint accessPoint : accessPoints) { 643 // Ignore access points that are out of range. 644 if (accessPoint.getLevel() != -1) { 645 String key = accessPoint.getBssid(); 646 if (TextUtils.isEmpty(key)) { 647 key = accessPoint.getSsidStr(); 648 } 649 hasAvailableAccessPoints = true; 650 LongPressAccessPointPreference pref = (LongPressAccessPointPreference) 651 getCachedPreference(key); 652 if (pref != null) { 653 pref.setOrder(index++); 654 continue; 655 } 656 LongPressAccessPointPreference 657 preference = new LongPressAccessPointPreference(accessPoint, 658 getPrefContext(), mUserBadgeCache, false, 659 R.drawable.ic_wifi_signal_0, this); 660 preference.setKey(key); 661 preference.setOrder(index++); 662 if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr()) 663 && !accessPoint.isSaved() 664 && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { 665 onPreferenceTreeClick(preference); 666 mOpenSsid = null; 667 } 668 getPreferenceScreen().addPreference(preference); 669 accessPoint.setListener(this); 670 preference.refresh(); 671 } 672 } 673 removeCachedPrefs(getPreferenceScreen()); 674 if (!hasAvailableAccessPoints) { 675 setProgressBarVisible(true); 676 Preference pref = new Preference(getContext()) { 677 @Override 678 public void onBindViewHolder(PreferenceViewHolder holder) { 679 super.onBindViewHolder(holder); 680 // Show a line on each side of add network. 681 holder.setDividerAllowedBelow(true); 682 } 683 }; 684 pref.setSelectable(false); 685 pref.setSummary(R.string.wifi_empty_list_wifi_on); 686 pref.setOrder(0); 687 pref.setKey(PREF_KEY_EMPTY_WIFI_LIST); 688 getPreferenceScreen().addPreference(pref); 689 mAddPreference.setOrder(1); 690 getPreferenceScreen().addPreference(mAddPreference); 691 } else { 692 mAddPreference.setOrder(index++); 693 getPreferenceScreen().addPreference(mAddPreference); 694 setProgressBarVisible(false); 695 } 696 if (mScanMenuItem != null) { 697 mScanMenuItem.setEnabled(true); 698 } 699 break; 700 701 case WifiManager.WIFI_STATE_ENABLING: 702 getPreferenceScreen().removeAll(); 703 setProgressBarVisible(true); 704 break; 705 706 case WifiManager.WIFI_STATE_DISABLING: 707 addMessagePreference(R.string.wifi_stopping); 708 setProgressBarVisible(true); 709 break; 710 711 case WifiManager.WIFI_STATE_DISABLED: 712 setOffMessage(); 713 setProgressBarVisible(false); 714 if (mScanMenuItem != null) { 715 mScanMenuItem.setEnabled(false); 716 } 717 break; 718 } 719 } 720 721 private void setOffMessage() { 722 if (isUiRestricted()) { 723 if (!isUiRestrictedByOnlyAdmin()) { 724 addMessagePreference(R.string.wifi_empty_list_user_restricted); 725 } 726 getPreferenceScreen().removeAll(); 727 return; 728 } 729 730 TextView emptyTextView = getEmptyTextView(); 731 if (emptyTextView == null) { 732 return; 733 } 734 735 final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off); 736 737 // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead, 738 // read the system settings directly. Because when the device is in Airplane mode, even if 739 // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off". 740 final ContentResolver resolver = getActivity().getContentResolver(); 741 final boolean wifiScanningMode = Settings.Global.getInt( 742 resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1; 743 744 if (!wifiScanningMode) { 745 // Show only the brief text if the user is not allowed to configure scanning settings, 746 // or the scanning mode has been turned off. 747 emptyTextView.setText(briefText, BufferType.SPANNABLE); 748 } else { 749 // Append the description of scanning settings with link. 750 final StringBuilder contentBuilder = new StringBuilder(); 751 contentBuilder.append(briefText); 752 contentBuilder.append("\n\n"); 753 contentBuilder.append(getText(R.string.wifi_scan_notify_text)); 754 LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() { 755 @Override 756 public void onClick() { 757 final SettingsActivity activity = 758 (SettingsActivity) WifiSettings.this.getActivity(); 759 activity.startPreferencePanel(ScanningSettings.class.getName(), null, 760 R.string.location_scanning_screen_title, null, null, 0); 761 } 762 }); 763 } 764 // Embolden and enlarge the brief description anyway. 765 Spannable boldSpan = (Spannable) emptyTextView.getText(); 766 boldSpan.setSpan( 767 new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0, 768 briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 769 getPreferenceScreen().removeAll(); 770 } 771 772 private void addMessagePreference(int messageId) { 773 TextView emptyTextView = getEmptyTextView(); 774 if (emptyTextView != null) emptyTextView.setText(messageId); 775 getPreferenceScreen().removeAll(); 776 } 777 778 protected void setProgressBarVisible(boolean visible) { 779 if (mProgressHeader != null) { 780 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 781 } 782 } 783 784 @Override 785 public void onWifiStateChanged(int state) { 786 switch (state) { 787 case WifiManager.WIFI_STATE_ENABLING: 788 addMessagePreference(R.string.wifi_starting); 789 setProgressBarVisible(true); 790 break; 791 792 case WifiManager.WIFI_STATE_DISABLED: 793 setOffMessage(); 794 setProgressBarVisible(false); 795 break; 796 } 797 } 798 799 @Override 800 public void onConnectedChanged() { 801 changeNextButtonState(mWifiTracker.isConnected()); 802 } 803 804 /** 805 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 806 * Wifi setup screens, not in usual wifi settings screen. 807 * 808 * @param enabled true when the device is connected to a wifi network. 809 */ 810 private void changeNextButtonState(boolean enabled) { 811 if (mEnableNextOnConnection && hasNextButton()) { 812 getNextButton().setEnabled(enabled); 813 } 814 } 815 816 @Override 817 public void onForget(WifiDialog dialog) { 818 forget(); 819 } 820 821 @Override 822 public void onSubmit(WifiDialog dialog) { 823 if (mDialog != null) { 824 submit(mDialog.getController()); 825 } 826 } 827 828 /* package */ void submit(WifiConfigController configController) { 829 830 final WifiConfiguration config = configController.getConfig(); 831 832 if (config == null) { 833 if (mSelectedAccessPoint != null 834 && mSelectedAccessPoint.isSaved()) { 835 connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */); 836 } 837 } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) { 838 mWifiManager.save(config, mSaveListener); 839 } else { 840 mWifiManager.save(config, mSaveListener); 841 if (mSelectedAccessPoint != null) { // Not an "Add network" 842 connect(config, false /* isSavedNetwork */); 843 } 844 } 845 846 mWifiTracker.resumeScanning(); 847 } 848 849 /* package */ void forget() { 850 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET); 851 if (!mSelectedAccessPoint.isSaved()) { 852 if (mSelectedAccessPoint.getNetworkInfo() != null && 853 mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) { 854 // Network is active but has no network ID - must be ephemeral. 855 mWifiManager.disableEphemeralNetwork( 856 AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr())); 857 } else { 858 // Should not happen, but a monkey seems to trigger it 859 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 860 return; 861 } 862 } else { 863 mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener); 864 } 865 866 mWifiTracker.resumeScanning(); 867 868 // We need to rename/replace "Next" button in wifi setup context. 869 changeNextButtonState(false); 870 } 871 872 protected void connect(final WifiConfiguration config, boolean isSavedNetwork) { 873 // Log subtype if configuration is a saved network. 874 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT, 875 isSavedNetwork); 876 mWifiManager.connect(config, mConnectListener); 877 } 878 879 protected void connect(final int networkId, boolean isSavedNetwork) { 880 // Log subtype if configuration is a saved network. 881 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT, 882 isSavedNetwork); 883 mWifiManager.connect(networkId, mConnectListener); 884 } 885 886 /** 887 * Called when "add network" button is pressed. 888 */ 889 /* package */ void onAddNetworkPressed() { 890 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK); 891 // No exact access point is selected. 892 mSelectedAccessPoint = null; 893 showDialog(null, WifiConfigUiBase.MODE_CONNECT); 894 } 895 896 @Override 897 protected int getHelpResource() { 898 return R.string.help_url_wifi; 899 } 900 901 @Override 902 public void onAccessPointChanged(final AccessPoint accessPoint) { 903 View view = getView(); 904 if (view != null) { 905 view.post(new Runnable() { 906 @Override 907 public void run() { 908 Object tag = accessPoint.getTag(); 909 if (tag != null) { 910 ((LongPressAccessPointPreference) tag).refresh(); 911 } 912 } 913 }); 914 } 915 } 916 917 @Override 918 public void onLevelChanged(AccessPoint accessPoint) { 919 ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged(); 920 } 921 922 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 923 new BaseSearchIndexProvider() { 924 @Override 925 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 926 final List<SearchIndexableRaw> result = new ArrayList<>(); 927 final Resources res = context.getResources(); 928 929 // Add fragment title 930 SearchIndexableRaw data = new SearchIndexableRaw(context); 931 data.title = res.getString(R.string.wifi_settings); 932 data.screenTitle = res.getString(R.string.wifi_settings); 933 data.keywords = res.getString(R.string.keywords_wifi); 934 result.add(data); 935 936 // Add saved Wi-Fi access points 937 final Collection<AccessPoint> accessPoints = 938 WifiTracker.getCurrentAccessPoints(context, true, false, false); 939 for (AccessPoint accessPoint : accessPoints) { 940 data = new SearchIndexableRaw(context); 941 data.title = accessPoint.getSsidStr(); 942 data.screenTitle = res.getString(R.string.wifi_settings); 943 data.enabled = enabled; 944 result.add(data); 945 } 946 947 return result; 948 } 949 }; 950 951 /** 952 * Returns true if the config is not editable through Settings. 953 * @param context Context of caller 954 * @param config The WiFi config. 955 * @return true if the config is not editable through Settings. 956 */ 957 static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) { 958 return !canModifyNetwork(context, config); 959 } 960 961 /** 962 * This method is a stripped version of WifiConfigStore.canModifyNetwork. 963 * TODO: refactor to have only one method. 964 * @param context Context of caller 965 * @param config The WiFi config. 966 * @return true if Settings can modify the config. 967 */ 968 static boolean canModifyNetwork(Context context, WifiConfiguration config) { 969 if (config == null) { 970 return true; 971 } 972 973 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 974 Context.DEVICE_POLICY_SERVICE); 975 976 // Check if device has DPM capability. If it has and dpm is still null, then we 977 // treat this case with suspicion and bail out. 978 final PackageManager pm = context.getPackageManager(); 979 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 980 return false; 981 } 982 983 boolean isConfigEligibleForLockdown = false; 984 if (dpm != null) { 985 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 986 if (deviceOwner != null) { 987 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 988 try { 989 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 990 deviceOwnerUserId); 991 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 992 } catch (NameNotFoundException e) { 993 // don't care 994 } 995 } 996 } 997 if (!isConfigEligibleForLockdown) { 998 return true; 999 } 1000 1001 final ContentResolver resolver = context.getContentResolver(); 1002 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 1003 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 1004 return !isLockdownFeatureEnabled; 1005 } 1006 1007 private static class SummaryProvider extends BroadcastReceiver 1008 implements SummaryLoader.SummaryProvider { 1009 1010 private final Context mContext; 1011 private final WifiManager mWifiManager; 1012 private final WifiStatusTracker mWifiTracker; 1013 private final SummaryLoader mSummaryLoader; 1014 1015 public SummaryProvider(Context context, SummaryLoader summaryLoader) { 1016 mContext = context; 1017 mSummaryLoader = summaryLoader; 1018 mWifiManager = context.getSystemService(WifiManager.class); 1019 mWifiTracker = new WifiStatusTracker(mWifiManager); 1020 } 1021 1022 private CharSequence getSummary() { 1023 if (!mWifiTracker.enabled) { 1024 return mContext.getString(R.string.wifi_disabled_generic); 1025 } 1026 if (!mWifiTracker.connected) { 1027 return mContext.getString(R.string.disconnected); 1028 } 1029 return mWifiTracker.ssid; 1030 } 1031 1032 @Override 1033 public void setListening(boolean listening) { 1034 if (listening) { 1035 IntentFilter filter = new IntentFilter(); 1036 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 1037 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 1038 filter.addAction(WifiManager.RSSI_CHANGED_ACTION); 1039 mSummaryLoader.registerReceiver(this, filter); 1040 } 1041 } 1042 1043 @Override 1044 public void onReceive(Context context, Intent intent) { 1045 mWifiTracker.handleBroadcast(intent); 1046 mSummaryLoader.setSummary(this, getSummary()); 1047 } 1048 } 1049 1050 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 1051 = new SummaryLoader.SummaryProviderFactory() { 1052 @Override 1053 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 1054 SummaryLoader summaryLoader) { 1055 return new SummaryProvider(activity, summaryLoader); 1056 } 1057 }; 1058} 1059