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