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