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