WifiSettings.java revision 9d1bfd1e8de6e46137a9571507c03526880d6a46
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 getPreferenceScreen().removeAll(); 637 return; 638 } 639 final int wifiState = mWifiManager.getWifiState(); 640 641 switch (wifiState) { 642 case WifiManager.WIFI_STATE_ENABLED: 643 // AccessPoints are automatically sorted with TreeSet. 644 final Collection<AccessPoint> accessPoints = 645 mWifiTracker.getAccessPoints(); 646 getPreferenceScreen().removeAll(); 647 648 boolean hasAvailableAccessPoints = false; 649 int index = 0; 650 for (AccessPoint accessPoint : accessPoints) { 651 // Ignore access points that are out of range. 652 if (accessPoint.getLevel() != -1) { 653 hasAvailableAccessPoints = true; 654 if (accessPoint.getTag() != null) { 655 final Preference pref = (Preference) accessPoint.getTag(); 656 pref.setOrder(index++); 657 getPreferenceScreen().addPreference(pref); 658 continue; 659 } 660 LongPressAccessPointPreference 661 preference = new LongPressAccessPointPreference(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 Preference pref = new Preference(getContext()) { 678 @Override 679 public void onBindViewHolder(PreferenceViewHolder holder) { 680 super.onBindViewHolder(holder); 681 // Show a line on each side of add network. 682 holder.setDividerAllowedBelow(true); 683 } 684 }; 685 pref.setSelectable(false); 686 pref.setSummary(R.string.wifi_empty_list_wifi_on); 687 pref.setOrder(0); 688 getPreferenceScreen().addPreference(pref); 689 mAddPreference.setOrder(1); 690 getPreferenceScreen().addPreference(mAddPreference); 691 } else { 692 mAddPreference.setOrder(index++); 693 getPreferenceScreen().addPreference(mAddPreference); 694 setProgressBarVisible(false); 695 } 696 if (mScanMenuItem != null) { 697 mScanMenuItem.setEnabled(true); 698 } 699 break; 700 701 case WifiManager.WIFI_STATE_ENABLING: 702 getPreferenceScreen().removeAll(); 703 setProgressBarVisible(true); 704 break; 705 706 case WifiManager.WIFI_STATE_DISABLING: 707 addMessagePreference(R.string.wifi_stopping); 708 setProgressBarVisible(true); 709 break; 710 711 case WifiManager.WIFI_STATE_DISABLED: 712 setOffMessage(); 713 setProgressBarVisible(false); 714 if (mScanMenuItem != null) { 715 mScanMenuItem.setEnabled(false); 716 } 717 break; 718 } 719 } 720 721 @Override 722 protected TextView initEmptyTextView() { 723 TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty); 724 emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); 725 return emptyView; 726 } 727 728 @Override 729 protected View initAdminSupportDetailsView() { 730 return getActivity().findViewById(R.id.admin_support_details); 731 } 732 733 private void setOffMessage() { 734 if (isUiRestricted()) { 735 getPreferenceScreen().removeAll(); 736 return; 737 } 738 739 TextView emptyTextView = getEmptyTextView(); 740 if (emptyTextView == null) { 741 return; 742 } 743 744 final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off); 745 746 // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead, 747 // read the system settings directly. Because when the device is in Airplane mode, even if 748 // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off". 749 final ContentResolver resolver = getActivity().getContentResolver(); 750 final boolean wifiScanningMode = Settings.Global.getInt( 751 resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1; 752 753 if (!wifiScanningMode) { 754 // Show only the brief text if the user is not allowed to configure scanning settings, 755 // or the scanning mode has been turned off. 756 emptyTextView.setText(briefText, BufferType.SPANNABLE); 757 } else { 758 // Append the description of scanning settings with link. 759 final StringBuilder contentBuilder = new StringBuilder(); 760 contentBuilder.append(briefText); 761 contentBuilder.append("\n\n"); 762 contentBuilder.append(getText(R.string.wifi_scan_notify_text)); 763 LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() { 764 @Override 765 public void onClick() { 766 final SettingsActivity activity = 767 (SettingsActivity) WifiSettings.this.getActivity(); 768 activity.startPreferencePanel(ScanningSettings.class.getName(), null, 769 R.string.location_scanning_screen_title, null, null, 0); 770 } 771 }); 772 } 773 // Embolden and enlarge the brief description anyway. 774 Spannable boldSpan = (Spannable) emptyTextView.getText(); 775 boldSpan.setSpan( 776 new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0, 777 briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 778 getPreferenceScreen().removeAll(); 779 } 780 781 private void addMessagePreference(int messageId) { 782 TextView emptyTextView = getEmptyTextView(); 783 if (emptyTextView != null) emptyTextView.setText(messageId); 784 getPreferenceScreen().removeAll(); 785 } 786 787 protected void setProgressBarVisible(boolean visible) { 788 if (mProgressHeader != null) { 789 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 790 } 791 } 792 793 @Override 794 public void onWifiStateChanged(int state) { 795 switch (state) { 796 case WifiManager.WIFI_STATE_ENABLING: 797 addMessagePreference(R.string.wifi_starting); 798 setProgressBarVisible(true); 799 break; 800 801 case WifiManager.WIFI_STATE_DISABLED: 802 setOffMessage(); 803 setProgressBarVisible(false); 804 break; 805 } 806 } 807 808 @Override 809 public void onConnectedChanged() { 810 changeNextButtonState(mWifiTracker.isConnected()); 811 } 812 813 /** 814 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 815 * Wifi setup screens, not in usual wifi settings screen. 816 * 817 * @param enabled true when the device is connected to a wifi network. 818 */ 819 private void changeNextButtonState(boolean enabled) { 820 if (mEnableNextOnConnection && hasNextButton()) { 821 getNextButton().setEnabled(enabled); 822 } 823 } 824 825 @Override 826 public void onForget(WifiDialog dialog) { 827 forget(); 828 } 829 830 @Override 831 public void onSubmit(WifiDialog dialog) { 832 if (mDialog != null) { 833 submit(mDialog.getController()); 834 } 835 } 836 837 /* package */ void submit(WifiConfigController configController) { 838 839 final WifiConfiguration config = configController.getConfig(); 840 841 if (config == null) { 842 if (mSelectedAccessPoint != null 843 && mSelectedAccessPoint.isSaved()) { 844 connect(mSelectedAccessPoint.getConfig()); 845 } 846 } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) { 847 mWifiManager.save(config, mSaveListener); 848 } else { 849 mWifiManager.save(config, mSaveListener); 850 if (mSelectedAccessPoint != null) { // Not an "Add network" 851 connect(config); 852 } 853 } 854 855 mWifiTracker.resumeScanning(); 856 } 857 858 /* package */ void forget() { 859 MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET); 860 if (!mSelectedAccessPoint.isSaved()) { 861 if (mSelectedAccessPoint.getNetworkInfo() != null && 862 mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) { 863 // Network is active but has no network ID - must be ephemeral. 864 mWifiManager.disableEphemeralNetwork( 865 AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr())); 866 } else { 867 // Should not happen, but a monkey seems to trigger it 868 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 869 return; 870 } 871 } else { 872 mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener); 873 } 874 875 mWifiTracker.resumeScanning(); 876 877 // We need to rename/replace "Next" button in wifi setup context. 878 changeNextButtonState(false); 879 } 880 881 protected void connect(final WifiConfiguration config) { 882 MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT); 883 mWifiManager.connect(config, mConnectListener); 884 } 885 886 protected void connect(final int networkId) { 887 MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT); 888 mWifiManager.connect(networkId, mConnectListener); 889 } 890 891 /** 892 * Called when "add network" button is pressed. 893 */ 894 /* package */ void onAddNetworkPressed() { 895 MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK); 896 // No exact access point is selected. 897 mSelectedAccessPoint = null; 898 showDialog(null, WifiConfigUiBase.MODE_CONNECT); 899 } 900 901 @Override 902 protected int getHelpResource() { 903 return R.string.help_url_wifi; 904 } 905 906 @Override 907 public void onAccessPointChanged(AccessPoint accessPoint) { 908 ((LongPressAccessPointPreference) accessPoint.getTag()).refresh(); 909 } 910 911 @Override 912 public void onLevelChanged(AccessPoint accessPoint) { 913 ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged(); 914 } 915 916 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 917 new BaseSearchIndexProvider() { 918 @Override 919 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 920 final List<SearchIndexableRaw> result = new ArrayList<>(); 921 final Resources res = context.getResources(); 922 923 // Add fragment title 924 SearchIndexableRaw data = new SearchIndexableRaw(context); 925 data.title = res.getString(R.string.wifi_settings); 926 data.screenTitle = res.getString(R.string.wifi_settings); 927 data.keywords = res.getString(R.string.keywords_wifi); 928 result.add(data); 929 930 // Add saved Wi-Fi access points 931 final Collection<AccessPoint> accessPoints = 932 WifiTracker.getCurrentAccessPoints(context, true, false, false); 933 for (AccessPoint accessPoint : accessPoints) { 934 data = new SearchIndexableRaw(context); 935 data.title = accessPoint.getSsidStr(); 936 data.screenTitle = res.getString(R.string.wifi_settings); 937 data.enabled = enabled; 938 result.add(data); 939 } 940 941 return result; 942 } 943 }; 944 945 /** 946 * Returns true if the config is not editable through Settings. 947 * @param context Context of caller 948 * @param config The WiFi config. 949 * @return true if the config is not editable through Settings. 950 */ 951 static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) { 952 return !canModifyNetwork(context, config); 953 } 954 955 /** 956 * This method is a stripped version of WifiConfigStore.canModifyNetwork. 957 * TODO: refactor to have only one method. 958 * @param context Context of caller 959 * @param config The WiFi config. 960 * @return true if Settings can modify the config. 961 */ 962 static boolean canModifyNetwork(Context context, WifiConfiguration config) { 963 if (config == null) { 964 return true; 965 } 966 967 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 968 Context.DEVICE_POLICY_SERVICE); 969 970 // Check if device has DPM capability. If it has and dpm is still null, then we 971 // treat this case with suspicion and bail out. 972 final PackageManager pm = context.getPackageManager(); 973 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 974 return false; 975 } 976 977 boolean isConfigEligibleForLockdown = false; 978 if (dpm != null) { 979 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 980 if (deviceOwner != null) { 981 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 982 try { 983 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 984 deviceOwnerUserId); 985 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 986 } catch (NameNotFoundException e) { 987 // don't care 988 } 989 } 990 } 991 if (!isConfigEligibleForLockdown) { 992 return true; 993 } 994 995 final ContentResolver resolver = context.getContentResolver(); 996 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 997 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 998 return !isLockdownFeatureEnabled; 999 } 1000 1001 private static class SummaryProvider extends BroadcastReceiver 1002 implements SummaryLoader.SummaryProvider { 1003 1004 private final Context mContext; 1005 private final WifiManager mWifiManager; 1006 private final WifiStatusTracker mWifiTracker; 1007 private final SummaryLoader mSummaryLoader; 1008 1009 public SummaryProvider(Context context, SummaryLoader summaryLoader) { 1010 mContext = context; 1011 mSummaryLoader = summaryLoader; 1012 mWifiManager = context.getSystemService(WifiManager.class); 1013 mWifiTracker = new WifiStatusTracker(mWifiManager); 1014 } 1015 1016 private CharSequence getSummary() { 1017 if (!mWifiTracker.enabled) { 1018 return mContext.getString(R.string.disabled); 1019 } 1020 if (!mWifiTracker.connected) { 1021 return mContext.getString(R.string.disconnected); 1022 } 1023 return mWifiTracker.ssid; 1024 } 1025 1026 @Override 1027 public void setListening(boolean listening) { 1028 if (listening) { 1029 IntentFilter filter = new IntentFilter(); 1030 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 1031 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 1032 filter.addAction(WifiManager.RSSI_CHANGED_ACTION); 1033 mContext.registerReceiver(this, filter); 1034 } else { 1035 mContext.unregisterReceiver(this); 1036 } 1037 } 1038 1039 @Override 1040 public void onReceive(Context context, Intent intent) { 1041 mWifiTracker.handleBroadcast(intent); 1042 mSummaryLoader.setSummary(this, getSummary()); 1043 } 1044 } 1045 1046 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 1047 = new SummaryLoader.SummaryProviderFactory() { 1048 @Override 1049 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 1050 SummaryLoader summaryLoader) { 1051 return new SummaryProvider(activity, summaryLoader); 1052 } 1053 }; 1054} 1055