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