WifiSettings.java revision d60f85daadc27df47d0fb04e73f18c50ac70f3c7
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.settings.wifi; 18 19import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; 20import static android.os.UserManager.DISALLOW_CONFIG_WIFI; 21 22import android.app.Activity; 23import android.app.Dialog; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.SharedPreferences; 30import android.content.res.Resources; 31import android.content.res.TypedArray; 32import android.location.LocationManager; 33import android.net.ConnectivityManager; 34import android.net.NetworkInfo; 35import android.net.NetworkInfo.DetailedState; 36import android.net.NetworkScoreManager; 37import android.net.NetworkScorerAppManager; 38import android.net.NetworkScorerAppManager.NetworkScorerAppData; 39import android.net.wifi.ScanResult; 40import android.net.wifi.WifiConfiguration; 41import android.net.wifi.WifiInfo; 42import android.net.wifi.WifiManager; 43import android.net.wifi.WpsInfo; 44import android.os.Bundle; 45import android.os.Handler; 46import android.os.Message; 47import android.os.Parcelable; 48import android.preference.Preference; 49import android.preference.PreferenceScreen; 50import android.util.Log; 51import android.view.ContextMenu; 52import android.view.ContextMenu.ContextMenuInfo; 53import android.view.LayoutInflater; 54import android.view.Menu; 55import android.view.MenuInflater; 56import android.view.MenuItem; 57import android.view.View; 58import android.view.View.OnClickListener; 59import android.widget.AdapterView.AdapterContextMenuInfo; 60import android.widget.Button; 61import android.widget.TextView; 62import android.widget.Toast; 63 64import com.android.settings.R; 65import com.android.settings.RestrictedSettingsFragment; 66import com.android.settings.SettingsActivity; 67import com.android.settings.search.BaseSearchIndexProvider; 68import com.android.settings.search.Indexable; 69import com.android.settings.search.SearchIndexableRaw; 70 71import java.util.ArrayList; 72import java.util.Collection; 73import java.util.Collections; 74import java.util.HashMap; 75import java.util.List; 76import java.util.concurrent.atomic.AtomicBoolean; 77 78/** 79 * Two types of UI are provided here. 80 * 81 * The first is for "usual Settings", appearing as any other Setup fragment. 82 * 83 * The second is for Setup Wizard, with a simplified interface that hides the action bar 84 * and menus. 85 */ 86public class WifiSettings extends RestrictedSettingsFragment 87 implements DialogInterface.OnClickListener, Indexable { 88 89 private static final String TAG = "WifiSettings"; 90 91 private static final int REQUEST_ENABLE_WIFI_ASSISTANT = 1; 92 93 /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST; 94 private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1; 95 private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2; 96 /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3; 97 private static final int MENU_ID_ADVANCED = Menu.FIRST + 4; 98 private static final int MENU_ID_SCAN = Menu.FIRST + 5; 99 private static final int MENU_ID_CONNECT = Menu.FIRST + 6; 100 private static final int MENU_ID_FORGET = Menu.FIRST + 7; 101 private static final int MENU_ID_MODIFY = Menu.FIRST + 8; 102 private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9; 103 104 private static final String KEY_ASSISTANT_DISMISS_TIME = "wifi_assistant_dismiss_time"; 105 private static final String KEY_ASSISTANT_START_TIME = "wifi_assistant_start_time"; 106 107 private static final long MILI_SECONDS_30_DAYS = 30L * 24L * 60L * 60L * 1000L; 108 private static final long MILI_SECONDS_90_DAYS = MILI_SECONDS_30_DAYS * 3L; 109 private static final long MILI_SECONDS_180_DAYS = MILI_SECONDS_90_DAYS * 2L; 110 111 public static final int WIFI_DIALOG_ID = 1; 112 /* package */ static final int WPS_PBC_DIALOG_ID = 2; 113 private static final int WPS_PIN_DIALOG_ID = 3; 114 private static final int WRITE_NFC_DIALOG_ID = 6; 115 116 // Combo scans can take 5-6s to complete - set to 10s. 117 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 118 119 // Instance state keys 120 private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode"; 121 private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; 122 123 private static boolean savedNetworksExist; 124 125 private final IntentFilter mFilter; 126 private final BroadcastReceiver mReceiver; 127 private final Scanner mScanner; 128 129 /* package */ WifiManager mWifiManager; 130 private WifiManager.ActionListener mConnectListener; 131 private WifiManager.ActionListener mSaveListener; 132 private WifiManager.ActionListener mForgetListener; 133 134 private WifiEnabler mWifiEnabler; 135 // An access point being editted is stored here. 136 private AccessPoint mSelectedAccessPoint; 137 138 private DetailedState mLastState; 139 private WifiInfo mLastInfo; 140 141 private final AtomicBoolean mConnected = new AtomicBoolean(false); 142 143 private WifiDialog mDialog; 144 private WriteWifiConfigToNfcDialog mWifiToNfcDialog; 145 146 private TextView mEmptyView; 147 148 // this boolean extra specifies whether to disable the Next button when not connected. Used by 149 // account creation outside of setup wizard. 150 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; 151 152 // should Next button only be enabled when we have a connection? 153 private boolean mEnableNextOnConnection; 154 155 // Save the dialog details 156 private boolean mDlgEdit; 157 private AccessPoint mDlgAccessPoint; 158 private Bundle mAccessPointSavedState; 159 private View mWifiAssistantCard; 160 private NetworkScorerAppData mWifiAssistantApp; 161 162 /** verbose logging flag. this flag is set thru developer debugging options 163 * and used so as to assist with in-the-field WiFi connectivity debugging */ 164 public static int mVerboseLogging = 0; 165 166 /* End of "used in Wifi Setup context" */ 167 168 /** A restricted multimap for use in constructAccessPoints */ 169 private static class Multimap<K,V> { 170 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 171 /** retrieve a non-null list of values with key K */ 172 List<V> getAll(K key) { 173 List<V> values = store.get(key); 174 return values != null ? values : Collections.<V>emptyList(); 175 } 176 177 void put(K key, V val) { 178 List<V> curVals = store.get(key); 179 if (curVals == null) { 180 curVals = new ArrayList<V>(3); 181 store.put(key, curVals); 182 } 183 curVals.add(val); 184 } 185 } 186 187 private static class Scanner extends Handler { 188 private int mRetry = 0; 189 private WifiSettings mWifiSettings = null; 190 191 Scanner(WifiSettings wifiSettings) { 192 mWifiSettings = wifiSettings; 193 } 194 195 void resume() { 196 if (!hasMessages(0)) { 197 sendEmptyMessage(0); 198 } 199 } 200 201 void forceScan() { 202 removeMessages(0); 203 sendEmptyMessage(0); 204 } 205 206 void pause() { 207 mRetry = 0; 208 removeMessages(0); 209 } 210 211 @Override 212 public void handleMessage(Message message) { 213 if (mWifiSettings.mWifiManager.startScan()) { 214 mRetry = 0; 215 } else if (++mRetry >= 3) { 216 mRetry = 0; 217 Activity activity = mWifiSettings.getActivity(); 218 if (activity != null) { 219 Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 220 } 221 return; 222 } 223 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 224 } 225 } 226 227 public WifiSettings() { 228 super(DISALLOW_CONFIG_WIFI); 229 mFilter = new IntentFilter(); 230 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 231 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 232 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); 233 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 234 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 235 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); 236 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 237 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 238 239 mReceiver = new BroadcastReceiver() { 240 @Override 241 public void onReceive(Context context, Intent intent) { 242 handleEvent(intent); 243 } 244 }; 245 246 mScanner = new Scanner(this); 247 } 248 249 @Override 250 public void onActivityCreated(Bundle savedInstanceState) { 251 super.onActivityCreated(savedInstanceState); 252 253 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 254 255 mConnectListener = new WifiManager.ActionListener() { 256 @Override 257 public void onSuccess() { 258 } 259 @Override 260 public void onFailure(int reason) { 261 Activity activity = getActivity(); 262 if (activity != null) { 263 Toast.makeText(activity, 264 R.string.wifi_failed_connect_message, 265 Toast.LENGTH_SHORT).show(); 266 } 267 } 268 }; 269 270 mSaveListener = new WifiManager.ActionListener() { 271 @Override 272 public void onSuccess() { 273 } 274 @Override 275 public void onFailure(int reason) { 276 Activity activity = getActivity(); 277 if (activity != null) { 278 Toast.makeText(activity, 279 R.string.wifi_failed_save_message, 280 Toast.LENGTH_SHORT).show(); 281 } 282 } 283 }; 284 285 mForgetListener = new WifiManager.ActionListener() { 286 @Override 287 public void onSuccess() { 288 } 289 @Override 290 public void onFailure(int reason) { 291 Activity activity = getActivity(); 292 if (activity != null) { 293 Toast.makeText(activity, 294 R.string.wifi_failed_forget_message, 295 Toast.LENGTH_SHORT).show(); 296 } 297 } 298 }; 299 300 if (savedInstanceState != null) { 301 mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE); 302 if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { 303 mAccessPointSavedState = 304 savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); 305 } 306 } 307 308 // if we're supposed to enable/disable the Next button based on our current connection 309 // state, start it off in the right state 310 Intent intent = getActivity().getIntent(); 311 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); 312 313 if (mEnableNextOnConnection) { 314 if (hasNextButton()) { 315 final ConnectivityManager connectivity = (ConnectivityManager) 316 getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); 317 if (connectivity != null) { 318 NetworkInfo info = connectivity.getNetworkInfo( 319 ConnectivityManager.TYPE_WIFI); 320 changeNextButtonState(info.isConnected()); 321 } 322 } 323 } 324 325 addPreferencesFromResource(R.xml.wifi_settings); 326 327 prepareWifiAssistantCard(); 328 329 mEmptyView = (TextView) getView().findViewById(android.R.id.empty); 330 getListView().setEmptyView(mEmptyView); 331 registerForContextMenu(getListView()); 332 setHasOptionsMenu(true); 333 } 334 335 @Override 336 public void onActivityResult(int requestCode, int resultCode, Intent resultData) { 337 if (requestCode == REQUEST_ENABLE_WIFI_ASSISTANT) { 338 if (resultCode == Activity.RESULT_OK) { 339 setWifiAssistantTimeout(); 340 getListView().removeHeaderView(mWifiAssistantCard); 341 mWifiAssistantApp = null; 342 } 343 } else { 344 super.onActivityResult(requestCode, resultCode, resultData); 345 } 346 } 347 348 @Override 349 public void onDestroyView() { 350 super.onDestroyView(); 351 352 if (mWifiEnabler != null) { 353 mWifiEnabler.teardownSwitchBar(); 354 } 355 } 356 357 @Override 358 public void onStart() { 359 super.onStart(); 360 361 // On/off switch is hidden for Setup Wizard (returns null) 362 mWifiEnabler = createWifiEnabler(); 363 } 364 365 /** 366 * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard) 367 */ 368 /* package */ WifiEnabler createWifiEnabler() { 369 final SettingsActivity activity = (SettingsActivity) getActivity(); 370 return new WifiEnabler(activity, activity.getSwitchBar()); 371 } 372 373 @Override 374 public void onResume() { 375 final Activity activity = getActivity(); 376 super.onResume(); 377 if (mWifiEnabler != null) { 378 mWifiEnabler.resume(activity); 379 } 380 381 activity.registerReceiver(mReceiver, mFilter); 382 updateAccessPoints(); 383 } 384 385 @Override 386 public void onPause() { 387 super.onPause(); 388 if (mWifiEnabler != null) { 389 mWifiEnabler.pause(); 390 } 391 392 getActivity().unregisterReceiver(mReceiver); 393 mScanner.pause(); 394 } 395 396 @Override 397 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 398 // If the user is not allowed to configure wifi, do not show the menu. 399 if (isUiRestricted()) return; 400 401 addOptionsMenuItems(menu); 402 super.onCreateOptionsMenu(menu, inflater); 403 } 404 405 /** 406 * @param menu 407 */ 408 void addOptionsMenuItems(Menu menu) { 409 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 410 TypedArray ta = getActivity().getTheme().obtainStyledAttributes( 411 new int[] {R.attr.ic_menu_add, R.attr.ic_wps}); 412 menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network) 413 .setIcon(ta.getDrawable(0)) 414 .setEnabled(wifiIsEnabled) 415 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 416 if (savedNetworksExist) { 417 menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label) 418 .setIcon(ta.getDrawable(0)) 419 .setEnabled(wifiIsEnabled) 420 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 421 } 422 menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh) 423 .setEnabled(wifiIsEnabled) 424 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 425 menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) 426 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 427 ta.recycle(); 428 } 429 430 @Override 431 public void onSaveInstanceState(Bundle outState) { 432 super.onSaveInstanceState(outState); 433 434 // If the dialog is showing, save its state. 435 if (mDialog != null && mDialog.isShowing()) { 436 outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit); 437 if (mDlgAccessPoint != null) { 438 mAccessPointSavedState = new Bundle(); 439 mDlgAccessPoint.saveWifiState(mAccessPointSavedState); 440 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState); 441 } 442 } 443 } 444 445 @Override 446 public boolean onOptionsItemSelected(MenuItem item) { 447 // If the user is not allowed to configure wifi, do not handle menu selections. 448 if (isUiRestricted()) return false; 449 450 switch (item.getItemId()) { 451 case MENU_ID_WPS_PBC: 452 showDialog(WPS_PBC_DIALOG_ID); 453 return true; 454 /* 455 case MENU_ID_P2P: 456 if (getActivity() instanceof SettingsActivity) { 457 ((SettingsActivity) getActivity()).startPreferencePanel( 458 WifiP2pSettings.class.getCanonicalName(), 459 null, 460 R.string.wifi_p2p_settings_title, null, 461 this, 0); 462 } else { 463 startFragment(this, WifiP2pSettings.class.getCanonicalName(), 464 R.string.wifi_p2p_settings_title, -1, null); 465 } 466 return true; 467 */ 468 case MENU_ID_WPS_PIN: 469 showDialog(WPS_PIN_DIALOG_ID); 470 return true; 471 case MENU_ID_SCAN: 472 if (mWifiManager.isWifiEnabled()) { 473 mScanner.forceScan(); 474 } 475 return true; 476 case MENU_ID_ADD_NETWORK: 477 if (mWifiManager.isWifiEnabled()) { 478 onAddNetworkPressed(); 479 } 480 return true; 481 case MENU_ID_SAVED_NETWORK: 482 if (getActivity() instanceof SettingsActivity) { 483 ((SettingsActivity) getActivity()).startPreferencePanel( 484 SavedAccessPointsWifiSettings.class.getCanonicalName(), null, 485 R.string.wifi_saved_access_points_titlebar, null, this, 0); 486 } else { 487 startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(), 488 R.string.wifi_saved_access_points_titlebar, 489 -1 /* Do not request a result */, null); 490 } 491 return true; 492 case MENU_ID_ADVANCED: 493 if (getActivity() instanceof SettingsActivity) { 494 ((SettingsActivity) getActivity()).startPreferencePanel( 495 AdvancedWifiSettings.class.getCanonicalName(), null, 496 R.string.wifi_advanced_titlebar, null, this, 0); 497 } else { 498 startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), 499 R.string.wifi_advanced_titlebar, -1 /* Do not request a results */, 500 null); 501 } 502 return true; 503 } 504 return super.onOptionsItemSelected(item); 505 } 506 507 @Override 508 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { 509 if (info instanceof AdapterContextMenuInfo) { 510 Preference preference = (Preference) getListView().getItemAtPosition( 511 ((AdapterContextMenuInfo) info).position); 512 513 if (preference instanceof AccessPoint) { 514 mSelectedAccessPoint = (AccessPoint) preference; 515 menu.setHeaderTitle(mSelectedAccessPoint.ssid); 516 if (mSelectedAccessPoint.getLevel() != -1 517 && mSelectedAccessPoint.getState() == null) { 518 menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); 519 } 520 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 521 menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); 522 menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); 523 524 if (mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) { 525 // Only allow writing of NFC tags for password-protected networks. 526 menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc); 527 } 528 } 529 } 530 } 531 } 532 533 @Override 534 public boolean onContextItemSelected(MenuItem item) { 535 if (mSelectedAccessPoint == null) { 536 return super.onContextItemSelected(item); 537 } 538 switch (item.getItemId()) { 539 case MENU_ID_CONNECT: { 540 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 541 connect(mSelectedAccessPoint.networkId); 542 } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) { 543 /** Bypass dialog for unsecured networks */ 544 mSelectedAccessPoint.generateOpenNetworkConfig(); 545 connect(mSelectedAccessPoint.getConfig()); 546 } else { 547 showDialog(mSelectedAccessPoint, true); 548 } 549 return true; 550 } 551 case MENU_ID_FORGET: { 552 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 553 return true; 554 } 555 case MENU_ID_MODIFY: { 556 showDialog(mSelectedAccessPoint, true); 557 return true; 558 } 559 case MENU_ID_WRITE_NFC: 560 showDialog(WRITE_NFC_DIALOG_ID); 561 return true; 562 563 } 564 return super.onContextItemSelected(item); 565 } 566 567 @Override 568 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { 569 if (preference instanceof AccessPoint) { 570 mSelectedAccessPoint = (AccessPoint) preference; 571 /** Bypass dialog for unsecured, unsaved networks */ 572 if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && 573 mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 574 mSelectedAccessPoint.generateOpenNetworkConfig(); 575 if (!savedNetworksExist) { 576 savedNetworksExist = true; 577 getActivity().invalidateOptionsMenu(); 578 } 579 connect(mSelectedAccessPoint.getConfig()); 580 } else { 581 showDialog(mSelectedAccessPoint, false); 582 } 583 } else { 584 return super.onPreferenceTreeClick(screen, preference); 585 } 586 return true; 587 } 588 589 private void showDialog(AccessPoint accessPoint, boolean edit) { 590 if (mDialog != null) { 591 removeDialog(WIFI_DIALOG_ID); 592 mDialog = null; 593 } 594 595 // Save the access point and edit mode 596 mDlgAccessPoint = accessPoint; 597 mDlgEdit = edit; 598 599 showDialog(WIFI_DIALOG_ID); 600 } 601 602 @Override 603 public Dialog onCreateDialog(int dialogId) { 604 switch (dialogId) { 605 case WIFI_DIALOG_ID: 606 AccessPoint ap = mDlgAccessPoint; // For manual launch 607 if (ap == null) { // For re-launch from saved state 608 if (mAccessPointSavedState != null) { 609 ap = new AccessPoint(getActivity(), mAccessPointSavedState); 610 // For repeated orientation changes 611 mDlgAccessPoint = ap; 612 // Reset the saved access point data 613 mAccessPointSavedState = null; 614 } 615 } 616 // If it's null, fine, it's for Add Network 617 mSelectedAccessPoint = ap; 618 mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); 619 return mDialog; 620 case WPS_PBC_DIALOG_ID: 621 return new WpsDialog(getActivity(), WpsInfo.PBC); 622 case WPS_PIN_DIALOG_ID: 623 return new WpsDialog(getActivity(), WpsInfo.DISPLAY); 624 case WRITE_NFC_DIALOG_ID: 625 if (mSelectedAccessPoint != null) { 626 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog( 627 getActivity(), mSelectedAccessPoint, mWifiManager); 628 return mWifiToNfcDialog; 629 } 630 631 } 632 return super.onCreateDialog(dialogId); 633 } 634 635 /** 636 * Shows the latest access points available with supplemental information like 637 * the strength of network and the security for it. 638 */ 639 private void updateAccessPoints() { 640 // Safeguard from some delayed event handling 641 if (getActivity() == null) return; 642 643 if (isUiRestricted()) { 644 addMessagePreference(R.string.wifi_empty_list_user_restricted); 645 return; 646 } 647 final int wifiState = mWifiManager.getWifiState(); 648 649 //when we update the screen, check if verbose logging has been turned on or off 650 mVerboseLogging = mWifiManager.getVerboseLoggingLevel(); 651 652 switch (wifiState) { 653 case WifiManager.WIFI_STATE_ENABLED: 654 // AccessPoints are automatically sorted with TreeSet. 655 final Collection<AccessPoint> accessPoints = 656 constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState); 657 getPreferenceScreen().removeAll(); 658 if (accessPoints.size() == 0) { 659 addMessagePreference(R.string.wifi_empty_list_wifi_on); 660 } 661 662 getListView().removeHeaderView(mWifiAssistantCard); 663 if (mWifiAssistantApp != null) { 664 getListView().addHeaderView(mWifiAssistantCard); 665 } 666 667 for (AccessPoint accessPoint : accessPoints) { 668 // Ignore access points that are out of range. 669 if (accessPoint.getLevel() != -1) { 670 getPreferenceScreen().addPreference(accessPoint); 671 } 672 } 673 break; 674 675 case WifiManager.WIFI_STATE_ENABLING: 676 getPreferenceScreen().removeAll(); 677 break; 678 679 case WifiManager.WIFI_STATE_DISABLING: 680 addMessagePreference(R.string.wifi_stopping); 681 break; 682 683 case WifiManager.WIFI_STATE_DISABLED: 684 setOffMessage(); 685 break; 686 } 687 } 688 689 /** 690 * Returns the Network Scorer for the Wifi Assistant App. 691 */ 692 public static NetworkScorerAppData getWifiAssistantApp(Context context) { 693 Collection<NetworkScorerAppData> scorers = 694 NetworkScorerAppManager.getAllValidScorers(context); 695 696 if (scorers.isEmpty()) { 697 return null; 698 } 699 700 // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first 701 // scorer on the system, we should allow the user to select one. 702 return scorers.iterator().next(); 703 } 704 705 private void prepareWifiAssistantCard() { 706 if (getActivity() instanceof WifiPickerActivity) { 707 return; 708 } 709 710 if (NetworkScorerAppManager.getActiveScorer(getActivity()) != null) { 711 // A scorer is already enabled; don't show the card. 712 return; 713 } 714 715 Collection<NetworkScorerAppData> scorers = 716 NetworkScorerAppManager.getAllValidScorers(getActivity()); 717 if (scorers.isEmpty()) { 718 // No scorers are available to enable; don't show the card. 719 return; 720 } 721 722 SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); 723 long lastTimeoutEndTime = sharedPreferences.getLong(KEY_ASSISTANT_START_TIME, 0); 724 long dismissTime = sharedPreferences.getLong(KEY_ASSISTANT_DISMISS_TIME, 0); 725 726 if ((System.currentTimeMillis() - lastTimeoutEndTime) <= dismissTime) { 727 return; 728 } 729 730 // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first 731 // scorer on the system, we should allow the user to select one. 732 mWifiAssistantApp = scorers.iterator().next(); 733 734 if (mWifiAssistantCard == null) { 735 mWifiAssistantCard = LayoutInflater.from(getActivity()) 736 .inflate(R.layout.wifi_assistant_card, getListView(), false); 737 Button setup = (Button) mWifiAssistantCard.findViewById(R.id.setup); 738 Button noThanks = (Button) mWifiAssistantCard.findViewById(R.id.no_thanks_button); 739 740 if (setup != null && noThanks != null) { 741 setup.setOnClickListener(new OnClickListener() { 742 @Override 743 public void onClick(View v) { 744 Intent intent = new Intent(); 745 if (mWifiAssistantApp.mConfigurationActivityClassName != null) { 746 // App has a custom configuration activity; launch that. 747 // This custom activity will be responsible for launching the system 748 // dialog. 749 intent.setClassName(mWifiAssistantApp.mPackageName, 750 mWifiAssistantApp.mConfigurationActivityClassName); 751 } else { 752 // Fall back on the system dialog. 753 intent.setAction(NetworkScoreManager.ACTION_CHANGE_ACTIVE); 754 intent.putExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME, 755 mWifiAssistantApp.mPackageName); 756 } 757 startActivityForResult(intent, REQUEST_ENABLE_WIFI_ASSISTANT); 758 } 759 }); 760 761 noThanks.setOnClickListener(new OnClickListener() { 762 @Override 763 public void onClick(View v) { 764 setWifiAssistantTimeout(); 765 getListView().removeHeaderView(mWifiAssistantCard); 766 mWifiAssistantApp = null; 767 } 768 }); 769 } 770 } 771 } 772 773 private void setWifiAssistantTimeout() { 774 SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); 775 SharedPreferences.Editor editor = sharedPreferences.edit(); 776 long dismissTime = sharedPreferences.getLong(KEY_ASSISTANT_DISMISS_TIME, 0); 777 778 if (dismissTime == 0) { 779 dismissTime = MILI_SECONDS_30_DAYS; 780 } else if (dismissTime == MILI_SECONDS_30_DAYS) { 781 dismissTime = MILI_SECONDS_90_DAYS; 782 } else if (dismissTime == MILI_SECONDS_90_DAYS) { 783 dismissTime = MILI_SECONDS_180_DAYS; 784 } else if (dismissTime == MILI_SECONDS_180_DAYS) { 785 dismissTime = java.lang.Long.MAX_VALUE; 786 } 787 788 editor.putLong(KEY_ASSISTANT_DISMISS_TIME, dismissTime); 789 editor.putLong(KEY_ASSISTANT_START_TIME, System.currentTimeMillis()); 790 editor.apply(); 791 } 792 793 private void setOffMessage() { 794 if (mEmptyView != null) { 795 mEmptyView.setCompoundDrawablesWithIntrinsicBounds(0, 796 R.drawable.ic_wifi_emptystate, 0, 0); 797 mEmptyView.setText(R.string.wifi_empty_list_wifi_off); 798 if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(), 799 android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) { 800 mEmptyView.append("\n\n"); 801 int resId; 802 if (android.provider.Settings.Secure.isLocationProviderEnabled( 803 getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) { 804 resId = R.string.wifi_scan_notify_text_location_on; 805 } else { 806 resId = R.string.wifi_scan_notify_text_location_off; 807 } 808 CharSequence charSeq = getText(resId); 809 mEmptyView.append(charSeq); 810 } 811 } 812 getPreferenceScreen().removeAll(); 813 } 814 815 private void addMessagePreference(int messageId) { 816 if (mEmptyView != null) mEmptyView.setText(messageId); 817 getPreferenceScreen().removeAll(); 818 } 819 820 /** Returns sorted list of access points */ 821 private static List<AccessPoint> constructAccessPoints(Context context, 822 WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) { 823 ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); 824 /** Lookup table to more quickly update AccessPoints by only considering objects with the 825 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 826 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 827 828 final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks(); 829 if (configs != null) { 830 savedNetworksExist = (configs.size() > 0); 831 for (WifiConfiguration config : configs) { 832 AccessPoint accessPoint = new AccessPoint(context, config); 833 if (lastInfo != null && lastState != null) { 834 accessPoint.update(lastInfo, lastState); 835 } 836 accessPoints.add(accessPoint); 837 apMap.put(accessPoint.ssid, accessPoint); 838 } 839 } 840 841 final List<ScanResult> results = wifiManager.getScanResults(); 842 if (results != null) { 843 for (ScanResult result : results) { 844 // Ignore hidden and ad-hoc networks. 845 if (result.SSID == null || result.SSID.length() == 0 || 846 result.capabilities.contains("[IBSS]")) { 847 continue; 848 } 849 850 boolean found = false; 851 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 852 if (accessPoint.update(result)) 853 found = true; 854 } 855 if (!found) { 856 AccessPoint accessPoint = new AccessPoint(context, result); 857 accessPoints.add(accessPoint); 858 apMap.put(accessPoint.ssid, accessPoint); 859 } 860 } 861 } 862 863 // Pre-sort accessPoints to speed preference insertion 864 Collections.sort(accessPoints); 865 return accessPoints; 866 } 867 868 private void handleEvent(Intent intent) { 869 String action = intent.getAction(); 870 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 871 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 872 WifiManager.WIFI_STATE_UNKNOWN)); 873 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 874 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 875 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 876 updateAccessPoints(); 877 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 878 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( 879 WifiManager.EXTRA_NETWORK_INFO); 880 mConnected.set(info.isConnected()); 881 changeNextButtonState(info.isConnected()); 882 updateAccessPoints(); 883 updateConnectionState(info.getDetailedState()); 884 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 885 updateConnectionState(null); 886 } 887 } 888 889 private void updateConnectionState(DetailedState state) { 890 /* sticky broadcasts can call this when wifi is disabled */ 891 if (!mWifiManager.isWifiEnabled()) { 892 mScanner.pause(); 893 return; 894 } 895 896 if (state == DetailedState.OBTAINING_IPADDR) { 897 mScanner.pause(); 898 } else { 899 mScanner.resume(); 900 } 901 902 mLastInfo = mWifiManager.getConnectionInfo(); 903 if (state != null) { 904 mLastState = state; 905 } 906 907 for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) { 908 // Maybe there's a WifiConfigPreference 909 Preference preference = getPreferenceScreen().getPreference(i); 910 if (preference instanceof AccessPoint) { 911 final AccessPoint accessPoint = (AccessPoint) preference; 912 accessPoint.update(mLastInfo, mLastState); 913 } 914 } 915 } 916 917 private void updateWifiState(int state) { 918 Activity activity = getActivity(); 919 if (activity != null) { 920 activity.invalidateOptionsMenu(); 921 } 922 923 switch (state) { 924 case WifiManager.WIFI_STATE_ENABLED: 925 mScanner.resume(); 926 return; // not break, to avoid the call to pause() below 927 928 case WifiManager.WIFI_STATE_ENABLING: 929 addMessagePreference(R.string.wifi_starting); 930 break; 931 932 case WifiManager.WIFI_STATE_DISABLED: 933 setOffMessage(); 934 break; 935 } 936 937 mLastInfo = null; 938 mLastState = null; 939 mScanner.pause(); 940 } 941 942 /** 943 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 944 * Wifi setup screens, not in usual wifi settings screen. 945 * 946 * @param enabled true when the device is connected to a wifi network. 947 */ 948 private void changeNextButtonState(boolean enabled) { 949 if (mEnableNextOnConnection && hasNextButton()) { 950 getNextButton().setEnabled(enabled); 951 } 952 } 953 954 @Override 955 public void onClick(DialogInterface dialogInterface, int button) { 956 if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { 957 forget(); 958 } else if (button == WifiDialog.BUTTON_SUBMIT) { 959 if (mDialog != null) { 960 submit(mDialog.getController()); 961 } 962 } 963 } 964 965 /* package */ void submit(WifiConfigController configController) { 966 967 final WifiConfiguration config = configController.getConfig(); 968 969 if (config == null) { 970 if (mSelectedAccessPoint != null 971 && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 972 connect(mSelectedAccessPoint.networkId); 973 } 974 } else if (config.networkId != INVALID_NETWORK_ID) { 975 if (mSelectedAccessPoint != null) { 976 mWifiManager.save(config, mSaveListener); 977 } 978 } else { 979 if (configController.isEdit()) { 980 mWifiManager.save(config, mSaveListener); 981 } else { 982 connect(config); 983 } 984 } 985 986 if (mWifiManager.isWifiEnabled()) { 987 mScanner.resume(); 988 } 989 updateAccessPoints(); 990 } 991 992 /* package */ void forget() { 993 if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 994 // Should not happen, but a monkey seems to trigger it 995 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 996 return; 997 } 998 999 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 1000 1001 if (mWifiManager.isWifiEnabled()) { 1002 mScanner.resume(); 1003 } 1004 updateAccessPoints(); 1005 1006 // We need to rename/replace "Next" button in wifi setup context. 1007 changeNextButtonState(false); 1008 } 1009 1010 protected void connect(final WifiConfiguration config) { 1011 mWifiManager.connect(config, mConnectListener); 1012 } 1013 1014 protected void connect(final int networkId) { 1015 mWifiManager.connect(networkId, mConnectListener); 1016 } 1017 1018 /** 1019 * Refreshes acccess points and ask Wifi module to scan networks again. 1020 */ 1021 /* package */ void refreshAccessPoints() { 1022 if (mWifiManager.isWifiEnabled()) { 1023 mScanner.resume(); 1024 } 1025 1026 getPreferenceScreen().removeAll(); 1027 } 1028 1029 /** 1030 * Called when "add network" button is pressed. 1031 */ 1032 /* package */ void onAddNetworkPressed() { 1033 // No exact access point is selected. 1034 mSelectedAccessPoint = null; 1035 showDialog(null, true); 1036 } 1037 1038 /* package */ int getAccessPointsCount() { 1039 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 1040 if (wifiIsEnabled) { 1041 return getPreferenceScreen().getPreferenceCount(); 1042 } else { 1043 return 0; 1044 } 1045 } 1046 1047 /** 1048 * Requests wifi module to pause wifi scan. May be ignored when the module is disabled. 1049 */ 1050 /* package */ void pauseWifiScan() { 1051 if (mWifiManager.isWifiEnabled()) { 1052 mScanner.pause(); 1053 } 1054 } 1055 1056 /** 1057 * Requests wifi module to resume wifi scan. May be ignored when the module is disabled. 1058 */ 1059 /* package */ void resumeWifiScan() { 1060 if (mWifiManager.isWifiEnabled()) { 1061 mScanner.resume(); 1062 } 1063 } 1064 1065 @Override 1066 protected int getHelpResource() { 1067 return R.string.help_url_wifi; 1068 } 1069 1070 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 1071 new BaseSearchIndexProvider() { 1072 @Override 1073 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 1074 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); 1075 final Resources res = context.getResources(); 1076 1077 // Add fragment title 1078 SearchIndexableRaw data = new SearchIndexableRaw(context); 1079 data.title = res.getString(R.string.wifi_settings); 1080 data.screenTitle = res.getString(R.string.wifi_settings); 1081 data.keywords = res.getString(R.string.keywords_wifi); 1082 result.add(data); 1083 1084 // Add available Wi-Fi access points 1085 WifiManager wifiManager = 1086 (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 1087 final Collection<AccessPoint> accessPoints = 1088 constructAccessPoints(context, wifiManager, null, null); 1089 for (AccessPoint accessPoint : accessPoints) { 1090 // We are indexing only the saved Wi-Fi networks. 1091 if (accessPoint.getConfig() == null) continue; 1092 data = new SearchIndexableRaw(context); 1093 data.title = accessPoint.getTitle().toString(); 1094 data.screenTitle = res.getString(R.string.wifi_settings); 1095 data.enabled = enabled; 1096 result.add(data); 1097 } 1098 1099 return result; 1100 } 1101 }; 1102} 1103