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