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