This is not right because wpa_supplicant states are transient (for instance when the WiFi Layer, at Layer 2, is going thru some harmless spurious disconnection cycle due to WiFi signal fluctuation). This cause the state of the WiFi Network to appear to be unstable to the user. Hence, I removed dependencies on wpa_supplicant internal state. In addition so as to improve debugging, I added the BSSID to the Wifi Verbose Logging string which is shown in wifi picker alongside the current network. This string only appear when a user goes into Developper Options and enable WiFi Verbose Logging. The below bug is an example of situation where a spurious disconnect (a coupld seconds) handled by wpa_supplicant can cause the WiFi Settings to indicate that the link is unstable. Bug:16140888 Wifi best network selection not smooth Change-Id: I0e7c6b86262b88ed993c46fcdcdbab4d9b1f5ea1
1099 lines
42 KiB
Java
1099 lines
42 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.wifi;
|
|
|
|
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
|
|
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Dialog;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.location.LocationManager;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.NetworkInfo;
|
|
import android.net.NetworkInfo.DetailedState;
|
|
import android.net.NetworkScoreManager;
|
|
import android.net.NetworkScorerAppManager;
|
|
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
|
import android.net.wifi.ScanResult;
|
|
import android.net.wifi.WifiConfiguration;
|
|
import android.net.wifi.WifiInfo;
|
|
import android.net.wifi.WifiManager;
|
|
import android.net.wifi.WpsInfo;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.preference.Preference;
|
|
import android.preference.PreferenceScreen;
|
|
import android.util.Log;
|
|
import android.view.ContextMenu;
|
|
import android.view.ContextMenu.ContextMenuInfo;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.RestrictedSettingsFragment;
|
|
import com.android.settings.SettingsActivity;
|
|
import com.android.settings.search.BaseSearchIndexProvider;
|
|
import com.android.settings.search.Indexable;
|
|
import com.android.settings.search.SearchIndexableRaw;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/**
|
|
* Two types of UI are provided here.
|
|
*
|
|
* The first is for "usual Settings", appearing as any other Setup fragment.
|
|
*
|
|
* The second is for Setup Wizard, with a simplified interface that hides the action bar
|
|
* and menus.
|
|
*/
|
|
public class WifiSettings extends RestrictedSettingsFragment
|
|
implements DialogInterface.OnClickListener, Indexable {
|
|
|
|
private static final String TAG = "WifiSettings";
|
|
|
|
private static final int REQUEST_ENABLE_WIFI_ASSISTANT = 1;
|
|
|
|
/* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
|
|
private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
|
|
private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
|
|
/* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
|
|
private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
|
|
private static final int MENU_ID_SCAN = Menu.FIRST + 5;
|
|
private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
|
|
private static final int MENU_ID_FORGET = Menu.FIRST + 7;
|
|
private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
|
|
private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
|
|
|
|
private static final String KEY_ASSISTANT_DISMISS_TIME = "wifi_assistant_dismiss_time";
|
|
private static final String KEY_ASSISTANT_START_TIME = "wifi_assistant_start_time";
|
|
|
|
private static final long MILI_SECONDS_30_DAYS = 30L * 24L * 60L * 60L * 1000L;
|
|
private static final long MILI_SECONDS_90_DAYS = MILI_SECONDS_30_DAYS * 3L;
|
|
private static final long MILI_SECONDS_180_DAYS = MILI_SECONDS_90_DAYS * 2L;
|
|
|
|
public static final int WIFI_DIALOG_ID = 1;
|
|
/* package */ static final int WPS_PBC_DIALOG_ID = 2;
|
|
private static final int WPS_PIN_DIALOG_ID = 3;
|
|
private static final int WRITE_NFC_DIALOG_ID = 6;
|
|
|
|
// Combo scans can take 5-6s to complete - set to 10s.
|
|
private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
|
|
|
|
// Instance state keys
|
|
private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
|
|
private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
|
|
|
|
private static boolean savedNetworksExist;
|
|
|
|
private final IntentFilter mFilter;
|
|
private final BroadcastReceiver mReceiver;
|
|
private final Scanner mScanner;
|
|
|
|
/* package */ WifiManager mWifiManager;
|
|
private WifiManager.ActionListener mConnectListener;
|
|
private WifiManager.ActionListener mSaveListener;
|
|
private WifiManager.ActionListener mForgetListener;
|
|
private boolean mP2pSupported;
|
|
|
|
private WifiEnabler mWifiEnabler;
|
|
// An access point being editted is stored here.
|
|
private AccessPoint mSelectedAccessPoint;
|
|
|
|
private DetailedState mLastState;
|
|
private WifiInfo mLastInfo;
|
|
|
|
private final AtomicBoolean mConnected = new AtomicBoolean(false);
|
|
|
|
private WifiDialog mDialog;
|
|
private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
|
|
|
|
private TextView mEmptyView;
|
|
|
|
// this boolean extra specifies whether to disable the Next button when not connected. Used by
|
|
// account creation outside of setup wizard.
|
|
private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
|
|
|
|
// should Next button only be enabled when we have a connection?
|
|
private boolean mEnableNextOnConnection;
|
|
|
|
// Save the dialog details
|
|
private boolean mDlgEdit;
|
|
private AccessPoint mDlgAccessPoint;
|
|
private Bundle mAccessPointSavedState;
|
|
private Preference mWifiAssistantPreference;
|
|
private NetworkScorerAppData mWifiAssistantApp;
|
|
|
|
/** verbose logging flag. this flag is set thru developer debugging options
|
|
* and used so as to assist with in-the-field WiFi connectivity debugging */
|
|
public static int mVerboseLogging = 0;
|
|
|
|
/* End of "used in Wifi Setup context" */
|
|
|
|
/** Holds the Wifi Assistant Card. */
|
|
private class WifiAssistantPreference extends Preference {
|
|
public WifiAssistantPreference() {
|
|
super(getActivity());
|
|
setLayoutResource(R.layout.wifi_assistant_card);
|
|
}
|
|
|
|
@Override
|
|
public void onBindView(View view) {
|
|
super.onBindView(view);
|
|
Button setup = (Button)view.findViewById(R.id.setup);
|
|
Button noThanks = (Button)view.findViewById(R.id.no_thanks_button);
|
|
|
|
if (setup != null && noThanks != null) {
|
|
setup.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
Intent intent = new Intent();
|
|
if (mWifiAssistantApp.mConfigurationActivityClassName != null) {
|
|
// App has a custom configuration activity; launch that.
|
|
// This custom activity will be responsible for launching the system
|
|
// dialog.
|
|
intent.setClassName(mWifiAssistantApp.mPackageName,
|
|
mWifiAssistantApp.mConfigurationActivityClassName);
|
|
} else {
|
|
// Fall back on the system dialog.
|
|
intent.setAction(NetworkScoreManager.ACTION_CHANGE_ACTIVE);
|
|
intent.putExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME,
|
|
mWifiAssistantApp.mPackageName);
|
|
}
|
|
startActivityForResult(intent, REQUEST_ENABLE_WIFI_ASSISTANT);
|
|
}
|
|
});
|
|
|
|
noThanks.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
setWifiAssistantTimeout();
|
|
getPreferenceScreen().removePreference(WifiAssistantPreference.this);
|
|
mWifiAssistantApp = null;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/** A restricted multimap for use in constructAccessPoints */
|
|
private static class Multimap<K,V> {
|
|
private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
|
|
/** retrieve a non-null list of values with key K */
|
|
List<V> getAll(K key) {
|
|
List<V> values = store.get(key);
|
|
return values != null ? values : Collections.<V>emptyList();
|
|
}
|
|
|
|
void put(K key, V val) {
|
|
List<V> curVals = store.get(key);
|
|
if (curVals == null) {
|
|
curVals = new ArrayList<V>(3);
|
|
store.put(key, curVals);
|
|
}
|
|
curVals.add(val);
|
|
}
|
|
}
|
|
|
|
private static class Scanner extends Handler {
|
|
private int mRetry = 0;
|
|
private WifiSettings mWifiSettings = null;
|
|
|
|
Scanner(WifiSettings wifiSettings) {
|
|
mWifiSettings = wifiSettings;
|
|
}
|
|
|
|
void resume() {
|
|
if (!hasMessages(0)) {
|
|
sendEmptyMessage(0);
|
|
}
|
|
}
|
|
|
|
void forceScan() {
|
|
removeMessages(0);
|
|
sendEmptyMessage(0);
|
|
}
|
|
|
|
void pause() {
|
|
mRetry = 0;
|
|
removeMessages(0);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
if (mWifiSettings.mWifiManager.startScan()) {
|
|
mRetry = 0;
|
|
} else if (++mRetry >= 3) {
|
|
mRetry = 0;
|
|
Activity activity = mWifiSettings.getActivity();
|
|
if (activity != null) {
|
|
Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
|
|
}
|
|
return;
|
|
}
|
|
sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
|
|
}
|
|
}
|
|
|
|
public WifiSettings() {
|
|
super(DISALLOW_CONFIG_WIFI);
|
|
mFilter = new IntentFilter();
|
|
mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
|
|
mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
|
|
mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
|
|
|
|
mReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
handleEvent(context, intent);
|
|
}
|
|
};
|
|
|
|
mScanner = new Scanner(this);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
|
|
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
|
|
|
mConnectListener = new WifiManager.ActionListener() {
|
|
@Override
|
|
public void onSuccess() {
|
|
}
|
|
@Override
|
|
public void onFailure(int reason) {
|
|
Activity activity = getActivity();
|
|
if (activity != null) {
|
|
Toast.makeText(activity,
|
|
R.string.wifi_failed_connect_message,
|
|
Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
};
|
|
|
|
mSaveListener = new WifiManager.ActionListener() {
|
|
@Override
|
|
public void onSuccess() {
|
|
}
|
|
@Override
|
|
public void onFailure(int reason) {
|
|
Activity activity = getActivity();
|
|
if (activity != null) {
|
|
Toast.makeText(activity,
|
|
R.string.wifi_failed_save_message,
|
|
Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
};
|
|
|
|
mForgetListener = new WifiManager.ActionListener() {
|
|
@Override
|
|
public void onSuccess() {
|
|
}
|
|
@Override
|
|
public void onFailure(int reason) {
|
|
Activity activity = getActivity();
|
|
if (activity != null) {
|
|
Toast.makeText(activity,
|
|
R.string.wifi_failed_forget_message,
|
|
Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
};
|
|
|
|
if (savedInstanceState != null) {
|
|
mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
|
|
if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
|
|
mAccessPointSavedState =
|
|
savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
|
|
}
|
|
}
|
|
|
|
// if we're supposed to enable/disable the Next button based on our current connection
|
|
// state, start it off in the right state
|
|
Intent intent = getActivity().getIntent();
|
|
mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
|
|
|
|
if (mEnableNextOnConnection) {
|
|
if (hasNextButton()) {
|
|
final ConnectivityManager connectivity = (ConnectivityManager)
|
|
getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
if (connectivity != null) {
|
|
NetworkInfo info = connectivity.getNetworkInfo(
|
|
ConnectivityManager.TYPE_WIFI);
|
|
changeNextButtonState(info.isConnected());
|
|
}
|
|
}
|
|
}
|
|
|
|
addPreferencesFromResource(R.xml.wifi_settings);
|
|
|
|
prepareWifiAssistantCard();
|
|
|
|
mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
|
|
getListView().setEmptyView(mEmptyView);
|
|
registerForContextMenu(getListView());
|
|
setHasOptionsMenu(true);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
|
if (requestCode == REQUEST_ENABLE_WIFI_ASSISTANT) {
|
|
if (resultCode == Activity.RESULT_OK) {
|
|
setWifiAssistantTimeout();
|
|
getPreferenceScreen().removePreference(mWifiAssistantPreference);
|
|
mWifiAssistantApp = null;
|
|
}
|
|
} else {
|
|
super.onActivityResult(requestCode, resultCode, resultData);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDestroyView() {
|
|
super.onDestroyView();
|
|
|
|
if (mWifiEnabler != null) {
|
|
mWifiEnabler.teardownSwitchBar();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
|
|
// On/off switch is hidden for Setup Wizard (returns null)
|
|
mWifiEnabler = createWifiEnabler();
|
|
}
|
|
|
|
/**
|
|
* @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
|
|
*/
|
|
/* package */ WifiEnabler createWifiEnabler() {
|
|
final SettingsActivity activity = (SettingsActivity) getActivity();
|
|
return new WifiEnabler(activity, activity.getSwitchBar());
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
final Activity activity = getActivity();
|
|
super.onResume();
|
|
if (mWifiEnabler != null) {
|
|
mWifiEnabler.resume(activity);
|
|
}
|
|
|
|
activity.registerReceiver(mReceiver, mFilter);
|
|
updateAccessPoints();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
if (mWifiEnabler != null) {
|
|
mWifiEnabler.pause();
|
|
}
|
|
|
|
getActivity().unregisterReceiver(mReceiver);
|
|
mScanner.pause();
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
// If the user is not allowed to configure wifi, do not show the menu.
|
|
if (isUiRestricted()) return;
|
|
|
|
addOptionsMenuItems(menu);
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
}
|
|
|
|
/**
|
|
* @param menu
|
|
*/
|
|
void addOptionsMenuItems(Menu menu) {
|
|
final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
|
|
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
|
|
new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
|
|
menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
|
|
.setIcon(ta.getDrawable(0))
|
|
.setEnabled(wifiIsEnabled)
|
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
|
if (savedNetworksExist) {
|
|
menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
|
|
.setIcon(ta.getDrawable(0))
|
|
.setEnabled(wifiIsEnabled)
|
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
|
}
|
|
menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
|
|
.setEnabled(wifiIsEnabled)
|
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
|
menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
|
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
|
ta.recycle();
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
|
|
// If the dialog is showing, save its state.
|
|
if (mDialog != null && mDialog.isShowing()) {
|
|
outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
|
|
if (mDlgAccessPoint != null) {
|
|
mAccessPointSavedState = new Bundle();
|
|
mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
|
|
outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
// If the user is not allowed to configure wifi, do not handle menu selections.
|
|
if (isUiRestricted()) return false;
|
|
|
|
switch (item.getItemId()) {
|
|
case MENU_ID_WPS_PBC:
|
|
showDialog(WPS_PBC_DIALOG_ID);
|
|
return true;
|
|
/*
|
|
case MENU_ID_P2P:
|
|
if (getActivity() instanceof SettingsActivity) {
|
|
((SettingsActivity) getActivity()).startPreferencePanel(
|
|
WifiP2pSettings.class.getCanonicalName(),
|
|
null,
|
|
R.string.wifi_p2p_settings_title, null,
|
|
this, 0);
|
|
} else {
|
|
startFragment(this, WifiP2pSettings.class.getCanonicalName(),
|
|
R.string.wifi_p2p_settings_title, -1, null);
|
|
}
|
|
return true;
|
|
*/
|
|
case MENU_ID_WPS_PIN:
|
|
showDialog(WPS_PIN_DIALOG_ID);
|
|
return true;
|
|
case MENU_ID_SCAN:
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.forceScan();
|
|
}
|
|
return true;
|
|
case MENU_ID_ADD_NETWORK:
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
onAddNetworkPressed();
|
|
}
|
|
return true;
|
|
case MENU_ID_SAVED_NETWORK:
|
|
if (getActivity() instanceof SettingsActivity) {
|
|
((SettingsActivity) getActivity()).startPreferencePanel(
|
|
SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
|
|
R.string.wifi_saved_access_points_titlebar, null, this, 0);
|
|
} else {
|
|
startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
|
|
R.string.wifi_saved_access_points_titlebar,
|
|
-1 /* Do not request a result */, null);
|
|
}
|
|
return true;
|
|
case MENU_ID_ADVANCED:
|
|
if (getActivity() instanceof SettingsActivity) {
|
|
((SettingsActivity) getActivity()).startPreferencePanel(
|
|
AdvancedWifiSettings.class.getCanonicalName(), null,
|
|
R.string.wifi_advanced_titlebar, null, this, 0);
|
|
} else {
|
|
startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
|
|
R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
|
|
null);
|
|
}
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
|
|
if (info instanceof AdapterContextMenuInfo) {
|
|
Preference preference = (Preference) getListView().getItemAtPosition(
|
|
((AdapterContextMenuInfo) info).position);
|
|
|
|
if (preference instanceof AccessPoint) {
|
|
mSelectedAccessPoint = (AccessPoint) preference;
|
|
menu.setHeaderTitle(mSelectedAccessPoint.ssid);
|
|
if (mSelectedAccessPoint.getLevel() != -1
|
|
&& mSelectedAccessPoint.getState() == null) {
|
|
menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
|
|
}
|
|
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
|
|
menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
|
|
menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
|
|
|
|
if (mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
|
|
// Only allow writing of NFC tags for password-protected networks.
|
|
menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
if (mSelectedAccessPoint == null) {
|
|
return super.onContextItemSelected(item);
|
|
}
|
|
switch (item.getItemId()) {
|
|
case MENU_ID_CONNECT: {
|
|
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
|
|
connect(mSelectedAccessPoint.networkId);
|
|
} else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
|
|
/** Bypass dialog for unsecured networks */
|
|
mSelectedAccessPoint.generateOpenNetworkConfig();
|
|
connect(mSelectedAccessPoint.getConfig());
|
|
} else {
|
|
showDialog(mSelectedAccessPoint, true);
|
|
}
|
|
return true;
|
|
}
|
|
case MENU_ID_FORGET: {
|
|
mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
|
|
return true;
|
|
}
|
|
case MENU_ID_MODIFY: {
|
|
showDialog(mSelectedAccessPoint, true);
|
|
return true;
|
|
}
|
|
case MENU_ID_WRITE_NFC:
|
|
showDialog(WRITE_NFC_DIALOG_ID);
|
|
return true;
|
|
|
|
}
|
|
return super.onContextItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
|
|
if (preference instanceof AccessPoint) {
|
|
mSelectedAccessPoint = (AccessPoint) preference;
|
|
/** Bypass dialog for unsecured, unsaved networks */
|
|
if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
|
|
mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
|
|
mSelectedAccessPoint.generateOpenNetworkConfig();
|
|
if (!savedNetworksExist) {
|
|
savedNetworksExist = true;
|
|
getActivity().invalidateOptionsMenu();
|
|
}
|
|
connect(mSelectedAccessPoint.getConfig());
|
|
} else {
|
|
showDialog(mSelectedAccessPoint, false);
|
|
}
|
|
} else {
|
|
return super.onPreferenceTreeClick(screen, preference);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void showDialog(AccessPoint accessPoint, boolean edit) {
|
|
if (mDialog != null) {
|
|
removeDialog(WIFI_DIALOG_ID);
|
|
mDialog = null;
|
|
}
|
|
|
|
// Save the access point and edit mode
|
|
mDlgAccessPoint = accessPoint;
|
|
mDlgEdit = edit;
|
|
|
|
showDialog(WIFI_DIALOG_ID);
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(int dialogId) {
|
|
switch (dialogId) {
|
|
case WIFI_DIALOG_ID:
|
|
AccessPoint ap = mDlgAccessPoint; // For manual launch
|
|
if (ap == null) { // For re-launch from saved state
|
|
if (mAccessPointSavedState != null) {
|
|
ap = new AccessPoint(getActivity(), mAccessPointSavedState);
|
|
// For repeated orientation changes
|
|
mDlgAccessPoint = ap;
|
|
// Reset the saved access point data
|
|
mAccessPointSavedState = null;
|
|
}
|
|
}
|
|
// If it's null, fine, it's for Add Network
|
|
mSelectedAccessPoint = ap;
|
|
mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
|
|
return mDialog;
|
|
case WPS_PBC_DIALOG_ID:
|
|
return new WpsDialog(getActivity(), WpsInfo.PBC);
|
|
case WPS_PIN_DIALOG_ID:
|
|
return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
|
|
case WRITE_NFC_DIALOG_ID:
|
|
if (mSelectedAccessPoint != null) {
|
|
mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
|
|
getActivity(), mSelectedAccessPoint, mWifiManager);
|
|
return mWifiToNfcDialog;
|
|
}
|
|
|
|
}
|
|
return super.onCreateDialog(dialogId);
|
|
}
|
|
|
|
/**
|
|
* Shows the latest access points available with supplemental information like
|
|
* the strength of network and the security for it.
|
|
*/
|
|
private void updateAccessPoints() {
|
|
// Safeguard from some delayed event handling
|
|
if (getActivity() == null) return;
|
|
|
|
if (isUiRestricted()) {
|
|
addMessagePreference(R.string.wifi_empty_list_user_restricted);
|
|
return;
|
|
}
|
|
final int wifiState = mWifiManager.getWifiState();
|
|
|
|
//when we update the screen, check if verbose logging has been turned on or off
|
|
mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
|
|
|
|
switch (wifiState) {
|
|
case WifiManager.WIFI_STATE_ENABLED:
|
|
// AccessPoints are automatically sorted with TreeSet.
|
|
final Collection<AccessPoint> accessPoints =
|
|
constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState);
|
|
getPreferenceScreen().removeAll();
|
|
if (accessPoints.size() == 0) {
|
|
addMessagePreference(R.string.wifi_empty_list_wifi_on);
|
|
}
|
|
|
|
if (mWifiAssistantApp != null) {
|
|
getPreferenceScreen().addPreference(mWifiAssistantPreference);
|
|
}
|
|
|
|
for (AccessPoint accessPoint : accessPoints) {
|
|
// Ignore access points that are out of range.
|
|
if (accessPoint.getLevel() != -1) {
|
|
getPreferenceScreen().addPreference(accessPoint);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WifiManager.WIFI_STATE_ENABLING:
|
|
getPreferenceScreen().removeAll();
|
|
break;
|
|
|
|
case WifiManager.WIFI_STATE_DISABLING:
|
|
addMessagePreference(R.string.wifi_stopping);
|
|
break;
|
|
|
|
case WifiManager.WIFI_STATE_DISABLED:
|
|
setOffMessage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private boolean prepareWifiAssistantCard() {
|
|
if (mWifiAssistantPreference == null) {
|
|
mWifiAssistantPreference = new WifiAssistantPreference();
|
|
}
|
|
|
|
if (getActivity() instanceof WifiPickerActivity) {
|
|
return false;
|
|
}
|
|
|
|
if (NetworkScorerAppManager.getActiveScorer(getActivity()) != null) {
|
|
// A scorer is already enabled; don't show the card.
|
|
return false;
|
|
}
|
|
|
|
Collection<NetworkScorerAppData> scorers =
|
|
NetworkScorerAppManager.getAllValidScorers(getActivity());
|
|
if (scorers.isEmpty()) {
|
|
// No scorers are available to enable; don't show the card.
|
|
return false;
|
|
}
|
|
|
|
SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
|
|
long lastTimeoutEndTime = sharedPreferences.getLong(KEY_ASSISTANT_START_TIME, 0);
|
|
long dismissTime = sharedPreferences.getLong(KEY_ASSISTANT_DISMISS_TIME, 0);
|
|
|
|
boolean shouldShow = ((System.currentTimeMillis() - lastTimeoutEndTime) > dismissTime);
|
|
if (shouldShow) {
|
|
// TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first
|
|
// scorer on the system, we should allow the user to select one.
|
|
mWifiAssistantApp = scorers.iterator().next();
|
|
}
|
|
return shouldShow;
|
|
}
|
|
|
|
private void setWifiAssistantTimeout() {
|
|
SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
|
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
long dismissTime = sharedPreferences.getLong(KEY_ASSISTANT_DISMISS_TIME, 0);
|
|
|
|
if (dismissTime == 0) {
|
|
dismissTime = MILI_SECONDS_30_DAYS;
|
|
} else if (dismissTime == MILI_SECONDS_30_DAYS) {
|
|
dismissTime = MILI_SECONDS_90_DAYS;
|
|
} else if (dismissTime == MILI_SECONDS_90_DAYS) {
|
|
dismissTime = MILI_SECONDS_180_DAYS;
|
|
} else if (dismissTime == MILI_SECONDS_180_DAYS) {
|
|
dismissTime = java.lang.Long.MAX_VALUE;
|
|
}
|
|
|
|
editor.putLong(KEY_ASSISTANT_DISMISS_TIME, dismissTime);
|
|
editor.putLong(KEY_ASSISTANT_START_TIME, System.currentTimeMillis());
|
|
editor.apply();
|
|
}
|
|
|
|
private void setOffMessage() {
|
|
if (mEmptyView != null) {
|
|
mEmptyView.setCompoundDrawablesWithIntrinsicBounds(0,
|
|
R.drawable.ic_wifi_emptystate, 0, 0);
|
|
mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
|
|
if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
|
|
android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
|
|
mEmptyView.append("\n\n");
|
|
int resId;
|
|
if (android.provider.Settings.Secure.isLocationProviderEnabled(
|
|
getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
|
|
resId = R.string.wifi_scan_notify_text_location_on;
|
|
} else {
|
|
resId = R.string.wifi_scan_notify_text_location_off;
|
|
}
|
|
CharSequence charSeq = getText(resId);
|
|
mEmptyView.append(charSeq);
|
|
}
|
|
}
|
|
getPreferenceScreen().removeAll();
|
|
}
|
|
|
|
private void addMessagePreference(int messageId) {
|
|
if (mEmptyView != null) mEmptyView.setText(messageId);
|
|
getPreferenceScreen().removeAll();
|
|
}
|
|
|
|
/** Returns sorted list of access points */
|
|
private static List<AccessPoint> constructAccessPoints(Context context,
|
|
WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) {
|
|
ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
|
|
/** Lookup table to more quickly update AccessPoints by only considering objects with the
|
|
* correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
|
|
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
|
|
|
|
final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
|
|
if (configs != null) {
|
|
savedNetworksExist = (configs.size() > 0);
|
|
for (WifiConfiguration config : configs) {
|
|
AccessPoint accessPoint = new AccessPoint(context, config);
|
|
if (lastInfo != null && lastState != null) {
|
|
accessPoint.update(lastInfo, lastState);
|
|
}
|
|
accessPoints.add(accessPoint);
|
|
apMap.put(accessPoint.ssid, accessPoint);
|
|
}
|
|
}
|
|
|
|
final List<ScanResult> results = wifiManager.getScanResults();
|
|
if (results != null) {
|
|
for (ScanResult result : results) {
|
|
// Ignore hidden and ad-hoc networks.
|
|
if (result.SSID == null || result.SSID.length() == 0 ||
|
|
result.capabilities.contains("[IBSS]")) {
|
|
continue;
|
|
}
|
|
|
|
boolean found = false;
|
|
for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
|
|
if (accessPoint.update(result))
|
|
found = true;
|
|
}
|
|
if (!found) {
|
|
AccessPoint accessPoint = new AccessPoint(context, result);
|
|
accessPoints.add(accessPoint);
|
|
apMap.put(accessPoint.ssid, accessPoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pre-sort accessPoints to speed preference insertion
|
|
Collections.sort(accessPoints);
|
|
return accessPoints;
|
|
}
|
|
|
|
private void handleEvent(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
|
|
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
|
|
WifiManager.WIFI_STATE_UNKNOWN));
|
|
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
|
|
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
|
|
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
|
|
updateAccessPoints();
|
|
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
|
|
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
|
|
WifiManager.EXTRA_NETWORK_INFO);
|
|
mConnected.set(info.isConnected());
|
|
changeNextButtonState(info.isConnected());
|
|
updateAccessPoints();
|
|
updateConnectionState(info.getDetailedState());
|
|
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
|
|
updateConnectionState(null);
|
|
}
|
|
}
|
|
|
|
private void updateConnectionState(DetailedState state) {
|
|
/* sticky broadcasts can call this when wifi is disabled */
|
|
if (!mWifiManager.isWifiEnabled()) {
|
|
mScanner.pause();
|
|
return;
|
|
}
|
|
|
|
if (state == DetailedState.OBTAINING_IPADDR) {
|
|
mScanner.pause();
|
|
} else {
|
|
mScanner.resume();
|
|
}
|
|
|
|
mLastInfo = mWifiManager.getConnectionInfo();
|
|
if (state != null) {
|
|
mLastState = state;
|
|
}
|
|
|
|
for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
|
|
// Maybe there's a WifiConfigPreference
|
|
Preference preference = getPreferenceScreen().getPreference(i);
|
|
if (preference instanceof AccessPoint) {
|
|
final AccessPoint accessPoint = (AccessPoint) preference;
|
|
accessPoint.update(mLastInfo, mLastState);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void updateWifiState(int state) {
|
|
Activity activity = getActivity();
|
|
if (activity != null) {
|
|
activity.invalidateOptionsMenu();
|
|
}
|
|
|
|
switch (state) {
|
|
case WifiManager.WIFI_STATE_ENABLED:
|
|
mScanner.resume();
|
|
return; // not break, to avoid the call to pause() below
|
|
|
|
case WifiManager.WIFI_STATE_ENABLING:
|
|
addMessagePreference(R.string.wifi_starting);
|
|
break;
|
|
|
|
case WifiManager.WIFI_STATE_DISABLED:
|
|
setOffMessage();
|
|
break;
|
|
}
|
|
|
|
mLastInfo = null;
|
|
mLastState = null;
|
|
mScanner.pause();
|
|
}
|
|
|
|
/**
|
|
* Renames/replaces "Next" button when appropriate. "Next" button usually exists in
|
|
* Wifi setup screens, not in usual wifi settings screen.
|
|
*
|
|
* @param enabled true when the device is connected to a wifi network.
|
|
*/
|
|
private void changeNextButtonState(boolean enabled) {
|
|
if (mEnableNextOnConnection && hasNextButton()) {
|
|
getNextButton().setEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialogInterface, int button) {
|
|
if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
|
|
forget();
|
|
} else if (button == WifiDialog.BUTTON_SUBMIT) {
|
|
if (mDialog != null) {
|
|
submit(mDialog.getController());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* package */ void submit(WifiConfigController configController) {
|
|
|
|
final WifiConfiguration config = configController.getConfig();
|
|
|
|
if (config == null) {
|
|
if (mSelectedAccessPoint != null
|
|
&& mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
|
|
connect(mSelectedAccessPoint.networkId);
|
|
}
|
|
} else if (config.networkId != INVALID_NETWORK_ID) {
|
|
if (mSelectedAccessPoint != null) {
|
|
mWifiManager.save(config, mSaveListener);
|
|
}
|
|
} else {
|
|
if (configController.isEdit()) {
|
|
mWifiManager.save(config, mSaveListener);
|
|
} else {
|
|
connect(config);
|
|
}
|
|
}
|
|
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.resume();
|
|
}
|
|
updateAccessPoints();
|
|
}
|
|
|
|
/* package */ void forget() {
|
|
if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
|
|
// Should not happen, but a monkey seems to trigger it
|
|
Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
|
|
return;
|
|
}
|
|
|
|
mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
|
|
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.resume();
|
|
}
|
|
updateAccessPoints();
|
|
|
|
// We need to rename/replace "Next" button in wifi setup context.
|
|
changeNextButtonState(false);
|
|
}
|
|
|
|
protected void connect(final WifiConfiguration config) {
|
|
mWifiManager.connect(config, mConnectListener);
|
|
}
|
|
|
|
protected void connect(final int networkId) {
|
|
mWifiManager.connect(networkId, mConnectListener);
|
|
}
|
|
|
|
/**
|
|
* Refreshes acccess points and ask Wifi module to scan networks again.
|
|
*/
|
|
/* package */ void refreshAccessPoints() {
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.resume();
|
|
}
|
|
|
|
getPreferenceScreen().removeAll();
|
|
}
|
|
|
|
/**
|
|
* Called when "add network" button is pressed.
|
|
*/
|
|
/* package */ void onAddNetworkPressed() {
|
|
// No exact access point is selected.
|
|
mSelectedAccessPoint = null;
|
|
showDialog(null, true);
|
|
}
|
|
|
|
/* package */ int getAccessPointsCount() {
|
|
final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
|
|
if (wifiIsEnabled) {
|
|
return getPreferenceScreen().getPreferenceCount();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
|
|
*/
|
|
/* package */ void pauseWifiScan() {
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.pause();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
|
|
*/
|
|
/* package */ void resumeWifiScan() {
|
|
if (mWifiManager.isWifiEnabled()) {
|
|
mScanner.resume();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int getHelpResource() {
|
|
return R.string.help_url_wifi;
|
|
}
|
|
|
|
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
|
new BaseSearchIndexProvider() {
|
|
@Override
|
|
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
|
|
final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
|
|
final Resources res = context.getResources();
|
|
|
|
// Add fragment title
|
|
SearchIndexableRaw data = new SearchIndexableRaw(context);
|
|
data.title = res.getString(R.string.wifi_settings);
|
|
data.screenTitle = res.getString(R.string.wifi_settings);
|
|
data.keywords = res.getString(R.string.keywords_wifi);
|
|
result.add(data);
|
|
|
|
// Add available Wi-Fi access points
|
|
WifiManager wifiManager =
|
|
(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
|
final Collection<AccessPoint> accessPoints =
|
|
constructAccessPoints(context, wifiManager, null, null);
|
|
for (AccessPoint accessPoint : accessPoints) {
|
|
// We are indexing only the saved Wi-Fi networks.
|
|
if (accessPoint.getConfig() == null) continue;
|
|
data = new SearchIndexableRaw(context);
|
|
data.title = accessPoint.getTitle().toString();
|
|
data.screenTitle = res.getString(R.string.wifi_settings);
|
|
data.enabled = enabled;
|
|
result.add(data);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
}
|