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