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