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