summaryrefslogtreecommitdiffstats
path: root/service
diff options
context:
space:
mode:
Diffstat (limited to 'service')
-rw-r--r--service/Android.mk59
-rw-r--r--service/CleanSpec.mk45
-rw-r--r--service/java/com/android/server/wifi/NetworkUpdateResult.java70
-rw-r--r--service/java/com/android/server/wifi/README.txt30
-rw-r--r--service/java/com/android/server/wifi/StateChangeResult.java41
-rw-r--r--service/java/com/android/server/wifi/SupplicantStateTracker.java363
-rw-r--r--service/java/com/android/server/wifi/WifiApConfigStore.java212
-rw-r--r--service/java/com/android/server/wifi/WifiConfigStore.java2001
-rw-r--r--service/java/com/android/server/wifi/WifiController.java750
-rw-r--r--service/java/com/android/server/wifi/WifiMonitor.java940
-rw-r--r--service/java/com/android/server/wifi/WifiNative.java982
-rw-r--r--service/java/com/android/server/wifi/WifiNotificationController.java296
-rw-r--r--service/java/com/android/server/wifi/WifiService.java47
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java1591
-rw-r--r--service/java/com/android/server/wifi/WifiSettingsStore.java197
-rw-r--r--service/java/com/android/server/wifi/WifiStateMachine.java4419
-rw-r--r--service/java/com/android/server/wifi/WifiTrafficPoller.java188
-rw-r--r--service/java/com/android/server/wifi/WifiWatchdogStateMachine.java1210
-rw-r--r--service/java/com/android/server/wifi/p2p/WifiP2pService.java47
-rw-r--r--service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java2960
-rw-r--r--service/jni/com_android_server_wifi_WifiNative.cpp180
21 files changed, 16628 insertions, 0 deletions
diff --git a/service/Android.mk b/service/Android.mk
new file mode 100644
index 000000000..e8d9d5712
--- /dev/null
+++ b/service/Android.mk
@@ -0,0 +1,59 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/java
+LOCAL_SRC_FILES := $(call all-java-files-under, java) \
+ $(call all-Iaidl-files-under, java) \
+ $(call all-logtags-files-under, java)
+
+LOCAL_JNI_SHARED_LIBRARIES := libandroid_runtime
+LOCAL_JAVA_LIBRARIES := services
+LOCAL_REQUIRED_MODULES := services
+LOCAL_MODULE_TAGS :=
+LOCAL_MODULE := wifi-service
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Make the JNI part
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_REQUIRED_MODULES := libandroid_runtime libhardware_legacy
+
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast
+LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
+LOCAL_CPPFLAGS += -Wno-conversion-null
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE) \
+ $(call include-path-for, libhardware)/hardware \
+ $(call include-path-for, libhardware_legacy)/hardware_legacy \
+ libcore/include
+
+LOCAL_SHARED_LIBRARIES += \
+ libnativehelper \
+ libcutils \
+ libutils \
+ libhardware \
+ libhardware_legacy \
+ libandroid_runtime
+
+LOCAL_SRC_FILES := jni/com_android_server_wifi_WifiNative.cpp
+LOCAL_MODULE := libwifi-service
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/service/CleanSpec.mk b/service/CleanSpec.mk
new file mode 100644
index 000000000..70e8e55e1
--- /dev/null
+++ b/service/CleanSpec.mk
@@ -0,0 +1,45 @@
+# Copyright (C) 2011 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/wifi-service_intermediates)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/service/java/com/android/server/wifi/NetworkUpdateResult.java b/service/java/com/android/server/wifi/NetworkUpdateResult.java
new file mode 100644
index 000000000..63cc33f7b
--- /dev/null
+++ b/service/java/com/android/server/wifi/NetworkUpdateResult.java
@@ -0,0 +1,70 @@
+/*
+ * 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.server.wifi;
+
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
+class NetworkUpdateResult {
+ int netId;
+ boolean ipChanged;
+ boolean proxyChanged;
+ boolean isNewNetwork = false;
+
+ public NetworkUpdateResult(int id) {
+ netId = id;
+ ipChanged = false;
+ proxyChanged = false;
+ }
+
+ public NetworkUpdateResult(boolean ip, boolean proxy) {
+ netId = INVALID_NETWORK_ID;
+ ipChanged = ip;
+ proxyChanged = proxy;
+ }
+
+ public void setNetworkId(int id) {
+ netId = id;
+ }
+
+ public int getNetworkId() {
+ return netId;
+ }
+
+ public void setIpChanged(boolean ip) {
+ ipChanged = ip;
+ }
+
+ public boolean hasIpChanged() {
+ return ipChanged;
+ }
+
+ public void setProxyChanged(boolean proxy) {
+ proxyChanged = proxy;
+ }
+
+ public boolean hasProxyChanged() {
+ return proxyChanged;
+ }
+
+ public boolean isNewNetwork() {
+ return isNewNetwork;
+ }
+
+ public void setIsNewNetwork(boolean isNew) {
+ isNewNetwork = isNew;
+ }
+}
diff --git a/service/java/com/android/server/wifi/README.txt b/service/java/com/android/server/wifi/README.txt
new file mode 100644
index 000000000..0d74da192
--- /dev/null
+++ b/service/java/com/android/server/wifi/README.txt
@@ -0,0 +1,30 @@
+This code has moved from
+
+frameworks/base/services/java/com/android/server/wifi: gitk <SHA1 to be filled in later>
+
+Prior to that it was at
+
+frameworks/base/wifi/java/android/net/wifi: gitk ffadfb9ffdced62db215319d3edc7717802088fb
+
+////////////////////////////////////////////////////////////////
+
+Salient points about Wifi Service implementation
+
+WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
+
+WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
+
+WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
+
+WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
+
+WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
+
+WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
+
+Feature description:
+
+Scan-only mode with Wi-Fi turned off:
+ - Setup wizard opts user into allowing scanning for improved location. We show no further dialogs in setup wizard since the user has just opted into the feature. This is the reason WifiService listens to DEVICE_PROVISIONED setting.
+ - Once the user has his device provisioned, turning off Wi-Fi from settings or from a third party app will show up a dialog reminding the user that scan mode will be on even though Wi-Fi is being turned off. The user has the choice to turn this notification off.
+ - In the scan mode, the device continues to allow scanning from any app with Wi-Fi turned off. This is done by disabling all networks and allowing only scans to be passed.
diff --git a/service/java/com/android/server/wifi/StateChangeResult.java b/service/java/com/android/server/wifi/StateChangeResult.java
new file mode 100644
index 000000000..7d2f2b4a6
--- /dev/null
+++ b/service/java/com/android/server/wifi/StateChangeResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.server.wifi;
+
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiSsid;
+
+/**
+ * Stores supplicant state change information passed from WifiMonitor to
+ * a state machine. WifiStateMachine, SupplicantStateTracker and WpsStateMachine
+ * are example state machines that handle it.
+ * @hide
+ */
+public class StateChangeResult {
+ StateChangeResult(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState state) {
+ this.state = state;
+ this.wifiSsid= wifiSsid;
+ this.BSSID = BSSID;
+ this.networkId = networkId;
+ }
+
+ int networkId;
+ WifiSsid wifiSsid;
+ String BSSID;
+ SupplicantState state;
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStateTracker.java b/service/java/com/android/server/wifi/SupplicantStateTracker.java
new file mode 100644
index 000000000..f8048ffeb
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStateTracker.java
@@ -0,0 +1,363 @@
+/*
+ * 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.server.wifi;
+
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Tracks the state changes in supplicant and provides functionality
+ * that is based on these state changes:
+ * - detect a failed WPA handshake that loops indefinitely
+ * - authentication failure handling
+ */
+class SupplicantStateTracker extends StateMachine {
+
+ private static final String TAG = "SupplicantStateTracker";
+ private static final boolean DBG = false;
+
+ private WifiStateMachine mWifiStateMachine;
+ private WifiConfigStore mWifiConfigStore;
+ private int mAuthenticationFailuresCount = 0;
+ private int mAssociationRejectCount = 0;
+ /* Indicates authentication failure in supplicant broadcast.
+ * TODO: enhance auth failure reporting to include notification
+ * for all type of failures: EAP, WPS & WPA networks */
+ private boolean mAuthFailureInSupplicantBroadcast = false;
+
+ /* Maximum retries on a authentication failure notification */
+ private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
+
+ /* Maximum retries on assoc rejection events */
+ private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
+
+ /* Tracks if networks have been disabled during a connection */
+ private boolean mNetworksDisabledDuringConnect = false;
+
+ private Context mContext;
+
+ private State mUninitializedState = new UninitializedState();
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mDisconnectState = new DisconnectedState();
+ private State mScanState = new ScanState();
+ private State mHandshakeState = new HandshakeState();
+ private State mCompletedState = new CompletedState();
+ private State mDormantState = new DormantState();
+
+ public SupplicantStateTracker(Context c, WifiStateMachine wsm, WifiConfigStore wcs, Handler t) {
+ super(TAG, t.getLooper());
+
+ mContext = c;
+ mWifiStateMachine = wsm;
+ mWifiConfigStore = wcs;
+ addState(mDefaultState);
+ addState(mUninitializedState, mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mDisconnectState, mDefaultState);
+ addState(mScanState, mDefaultState);
+ addState(mHandshakeState, mDefaultState);
+ addState(mCompletedState, mDefaultState);
+ addState(mDormantState, mDefaultState);
+
+ setInitialState(mUninitializedState);
+ setLogRecSize(50);
+ setLogOnlyTransitions(true);
+ //start the state machine
+ start();
+ }
+
+ private void handleNetworkConnectionFailure(int netId, int disableReason) {
+ /* If other networks disabled during connection, enable them */
+ if (mNetworksDisabledDuringConnect) {
+ mWifiConfigStore.enableAllNetworks();
+ mNetworksDisabledDuringConnect = false;
+ }
+ /* Disable failed network */
+ mWifiConfigStore.disableNetwork(netId, disableReason);
+ }
+
+ private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
+ SupplicantState supState = (SupplicantState) stateChangeResult.state;
+
+ if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
+
+ switch (supState) {
+ case DISCONNECTED:
+ transitionTo(mDisconnectState);
+ break;
+ case INTERFACE_DISABLED:
+ //we should have received a disconnection already, do nothing
+ break;
+ case SCANNING:
+ transitionTo(mScanState);
+ break;
+ case AUTHENTICATING:
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ transitionTo(mHandshakeState);
+ break;
+ case COMPLETED:
+ transitionTo(mCompletedState);
+ break;
+ case DORMANT:
+ transitionTo(mDormantState);
+ break;
+ case INACTIVE:
+ transitionTo(mInactiveState);
+ break;
+ case UNINITIALIZED:
+ case INVALID:
+ transitionTo(mUninitializedState);
+ break;
+ default:
+ Log.e(TAG, "Unknown supplicant state " + supState);
+ break;
+ }
+ }
+
+ private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
+ Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
+ if (failedAuth) {
+ intent.putExtra(
+ WifiManager.EXTRA_SUPPLICANT_ERROR,
+ WifiManager.ERROR_AUTHENTICATING);
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /********************************************************
+ * HSM states
+ *******************************************************/
+
+ class DefaultState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ mAuthenticationFailuresCount++;
+ mAuthFailureInSupplicantBroadcast = true;
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+ mAuthFailureInSupplicantBroadcast = false;
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
+ transitionTo(mUninitializedState);
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ mNetworksDisabledDuringConnect = true;
+ mAssociationRejectCount = 0;
+ break;
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ mAssociationRejectCount++;
+ break;
+ default:
+ Log.e(TAG, "Ignoring " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ /*
+ * This indicates that the supplicant state as seen
+ * by the framework is not initialized yet. We are
+ * in this state right after establishing a control
+ * channel connection before any supplicant events
+ * or after we have lost the control channel
+ * connection to the supplicant
+ */
+ class UninitializedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class InactiveState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class DisconnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* If a disconnect event happens after authentication failure
+ * exceeds maximum retries, disable the network
+ */
+ Message message = getCurrentMessage();
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+ if (mAuthenticationFailuresCount >= MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ Log.d(TAG, "Failed to authenticate, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_AUTH_FAILURE);
+ mAuthenticationFailuresCount = 0;
+ }
+ else if (mAssociationRejectCount >= MAX_RETRIES_ON_ASSOCIATION_REJECT) {
+ Log.d(TAG, "Association getting rejected, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_ASSOCIATION_REJECT);
+ mAssociationRejectCount = 0;
+ }
+ }
+ }
+
+ class ScanState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class HandshakeState extends State {
+ /**
+ * The max number of the WPA supplicant loop iterations before we
+ * decide that the loop should be terminated:
+ */
+ private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+ private int mLoopDetectIndex;
+ private int mLoopDetectCount;
+
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ mLoopDetectIndex = 0;
+ mLoopDetectCount = 0;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ if (SupplicantState.isHandshakeState(state)) {
+ if (mLoopDetectIndex > state.ordinal()) {
+ mLoopDetectCount++;
+ }
+ if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+ Log.d(TAG, "Supplicant loop detected, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_AUTH_FAILURE);
+ }
+ mLoopDetectIndex = state.ordinal();
+ sendSupplicantStateChangedBroadcast(state,
+ mAuthFailureInSupplicantBroadcast);
+ } else {
+ //Have the DefaultState handle the transition
+ return NOT_HANDLED;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class CompletedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* Reset authentication failure count */
+ mAuthenticationFailuresCount = 0;
+ mAssociationRejectCount = 0;
+ if (mNetworksDisabledDuringConnect) {
+ mWifiConfigStore.enableAllNetworks();
+ mNetworksDisabledDuringConnect = false;
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+ /* Ignore any connecting state in completed state. Group re-keying
+ * events and other auth events that do not affect connectivity are
+ * ignored
+ */
+ if (SupplicantState.isConnecting(state)) {
+ break;
+ }
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
+ sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
+ transitionTo(mUninitializedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ //TODO: remove after getting rid of the state in supplicant
+ class DormantState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mAuthenticationFailuresCount " + mAuthenticationFailuresCount);
+ pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
+ pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
+ pw.println();
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
new file mode 100644
index 000000000..5e28fcfe5
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -0,0 +1,212 @@
+/*
+ * 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.server.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.R;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Provides API to the WifiStateMachine for doing read/write access
+ * to soft access point configuration
+ */
+class WifiApConfigStore extends StateMachine {
+
+ private Context mContext;
+ private static final String TAG = "WifiApConfigStore";
+
+ private static final String AP_CONFIG_FILE = Environment.getDataDirectory() +
+ "/misc/wifi/softap.conf";
+
+ private static final int AP_CONFIG_FILE_VERSION = 1;
+
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mActiveState = new ActiveState();
+
+ private WifiConfiguration mWifiApConfig = null;
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+
+ WifiApConfigStore(Context context, Handler target) {
+ super(TAG, target.getLooper());
+
+ mContext = context;
+ addState(mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mActiveState, mDefaultState);
+
+ setInitialState(mInactiveState);
+ }
+
+ public static WifiApConfigStore makeWifiApConfigStore(Context context, Handler target) {
+ WifiApConfigStore s = new WifiApConfigStore(context, target);
+ s.start();
+ return s;
+ }
+
+ class DefaultState extends State {
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
+ Log.e(TAG, "Unexpected message: " + message);
+ break;
+ case WifiStateMachine.CMD_REQUEST_AP_CONFIG:
+ mReplyChannel.replyToMessage(message,
+ WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);
+ break;
+ default:
+ Log.e(TAG, "Failed to handle " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ class InactiveState extends State {
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ mWifiApConfig = (WifiConfiguration) message.obj;
+ transitionTo(mActiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ActiveState extends State {
+ public void enter() {
+ new Thread(new Runnable() {
+ public void run() {
+ writeApConfiguration(mWifiApConfig);
+ sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED);
+ }
+ }).start();
+ }
+
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ //TODO: have feedback to the user when we do this
+ //to indicate the write is currently in progress
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ deferMessage(message);
+ break;
+ case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ void loadApConfiguration() {
+ DataInputStream in = null;
+ try {
+ WifiConfiguration config = new WifiConfiguration();
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ AP_CONFIG_FILE)));
+
+ int version = in.readInt();
+ if (version != 1) {
+ Log.e(TAG, "Bad version on hotspot configuration file, set defaults");
+ setDefaultApConfiguration();
+ return;
+ }
+ config.SSID = in.readUTF();
+ int authType = in.readInt();
+ config.allowedKeyManagement.set(authType);
+ if (authType != KeyMgmt.NONE) {
+ config.preSharedKey = in.readUTF();
+ }
+ mWifiApConfig = config;
+ } catch (IOException ignore) {
+ setDefaultApConfiguration();
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+
+ Messenger getMessenger() {
+ return new Messenger(getHandler());
+ }
+
+ private void writeApConfiguration(final WifiConfiguration config) {
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(AP_CONFIG_FILE)));
+
+ out.writeInt(AP_CONFIG_FILE_VERSION);
+ out.writeUTF(config.SSID);
+ int authType = config.getAuthType();
+ out.writeInt(authType);
+ if(authType != KeyMgmt.NONE) {
+ out.writeUTF(config.preSharedKey);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing hotspot configuration" + e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+
+ /* Generate a default WPA2 based configuration with a random password.
+ We are changing the Wifi Ap configuration storage from secure settings to a
+ flat file accessible only by the system. A WPA2 based default configuration
+ will keep the device secure after the update */
+ private void setDefaultApConfiguration() {
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+ config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
+ String randomUUID = UUID.randomUUID().toString();
+ //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9,13);
+ sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG, config);
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
new file mode 100644
index 000000000..6ce473288
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -0,0 +1,2001 @@
+/*
+ * 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.server.wifi;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
+import android.net.NetworkInfo.DetailedState;
+import android.net.ProxyProperties;
+import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.IpAssignment;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiConfiguration.Status;
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.WpsResult;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class provides the API to manage configured
+ * wifi networks. The API is not thread safe is being
+ * used only from WifiStateMachine.
+ *
+ * It deals with the following
+ * - Add/update/remove a WifiConfiguration
+ * The configuration contains two types of information.
+ * = IP and proxy configuration that is handled by WifiConfigStore and
+ * is saved to disk on any change.
+ *
+ * The format of configuration file is as follows:
+ * <version>
+ * <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ * <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ * ..
+ *
+ * (key, value) pairs for a given network are grouped together and can
+ * be in any order. A EOS at the end of a set of (key, value) pairs
+ * indicates that the next set of (key, value) pairs are for a new
+ * network. A network is identified by a unique ID_KEY. If there is no
+ * ID_KEY in the (key, value) pairs, the data is discarded.
+ *
+ * An invalid version on read would result in discarding the contents of
+ * the file. On the next write, the latest version is written to file.
+ *
+ * Any failures during read or write to the configuration file are ignored
+ * without reporting to the user since the likelihood of these errors are
+ * low and the impact on connectivity is low.
+ *
+ * = SSID & security details that is pushed to the supplicant.
+ * supplicant saves these details to the disk on calling
+ * saveConfigCommand().
+ *
+ * We have two kinds of APIs exposed:
+ * > public API calls that provide fine grained control
+ * - enableNetwork, disableNetwork, addOrUpdateNetwork(),
+ * removeNetwork(). For these calls, the config is not persisted
+ * to the disk. (TODO: deprecate these calls in WifiManager)
+ * > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
+ * These calls persist the supplicant config to disk.
+ *
+ * - Maintain a list of configured networks for quick access
+ *
+ */
+public class WifiConfigStore {
+
+ private Context mContext;
+ private static final String TAG = "WifiConfigStore";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
+
+ /* configured networks with network id as the key */
+ private HashMap<Integer, WifiConfiguration> mConfiguredNetworks =
+ new HashMap<Integer, WifiConfiguration>();
+
+ /* A network id is a unique identifier for a network configured in the
+ * supplicant. Network ids are generated when the supplicant reads
+ * the configuration file at start and can thus change for networks.
+ * We store the IP configuration for networks along with a unique id
+ * that is generated from SSID and security type of the network. A mapping
+ * from the generated unique id to network id of the network is needed to
+ * map supplicant config to IP configuration. */
+ private HashMap<Integer, Integer> mNetworkIds =
+ new HashMap<Integer, Integer>();
+
+ /* Tracks the highest priority of configured networks */
+ private int mLastPriority = -1;
+
+ private static final String ipConfigFile = Environment.getDataDirectory() +
+ "/misc/wifi/ipconfig.txt";
+
+ private static final int IPCONFIG_FILE_VERSION = 2;
+
+ /* IP and proxy configuration keys */
+ private static final String ID_KEY = "id";
+ private static final String IP_ASSIGNMENT_KEY = "ipAssignment";
+ private static final String LINK_ADDRESS_KEY = "linkAddress";
+ private static final String GATEWAY_KEY = "gateway";
+ private static final String DNS_KEY = "dns";
+ private static final String PROXY_SETTINGS_KEY = "proxySettings";
+ private static final String PROXY_HOST_KEY = "proxyHost";
+ private static final String PROXY_PORT_KEY = "proxyPort";
+ private static final String PROXY_PAC_FILE = "proxyPac";
+ private static final String EXCLUSION_LIST_KEY = "exclusionList";
+ private static final String EOS = "eos";
+
+
+ /* Enterprise configuration keys */
+ /**
+ * In old configurations, the "private_key" field was used. However, newer
+ * configurations use the key_id field with the engine_id set to "keystore".
+ * If this field is found in the configuration, the migration code is
+ * triggered.
+ */
+ public static final String OLD_PRIVATE_KEY_NAME = "private_key";
+
+ /** This represents an empty value of an enterprise field.
+ * NULL is used at wpa_supplicant to indicate an empty value
+ */
+ static final String EMPTY_VALUE = "NULL";
+
+ /** Internal use only */
+ private static final String[] ENTERPRISE_CONFIG_SUPPLICANT_KEYS = new String[] {
+ WifiEnterpriseConfig.EAP_KEY, WifiEnterpriseConfig.PHASE2_KEY,
+ WifiEnterpriseConfig.IDENTITY_KEY, WifiEnterpriseConfig.ANON_IDENTITY_KEY,
+ WifiEnterpriseConfig.PASSWORD_KEY, WifiEnterpriseConfig.CLIENT_CERT_KEY,
+ WifiEnterpriseConfig.CA_CERT_KEY, WifiEnterpriseConfig.SUBJECT_MATCH_KEY,
+ WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ID_KEY,
+ WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY };
+
+ private final LocalLog mLocalLog;
+ private final WpaConfigFileObserver mFileObserver;
+
+ private WifiNative mWifiNative;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ WifiConfigStore(Context c, WifiNative wn) {
+ mContext = c;
+ mWifiNative = wn;
+
+ if (VDBG) {
+ mLocalLog = mWifiNative.getLocalLog();
+ mFileObserver = new WpaConfigFileObserver();
+ mFileObserver.startWatching();
+ } else {
+ mLocalLog = null;
+ mFileObserver = null;
+ }
+ }
+
+ class WpaConfigFileObserver extends FileObserver {
+
+ public WpaConfigFileObserver() {
+ super(SUPPLICANT_CONFIG_FILE, CLOSE_WRITE);
+ }
+
+ @Override
+ public void onEvent(int event, String path) {
+ if (event == CLOSE_WRITE) {
+ File file = new File(SUPPLICANT_CONFIG_FILE);
+ if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
+ }
+ }
+ }
+
+
+ /**
+ * Fetch the list of configured networks
+ * and enable all stored networks in supplicant.
+ */
+ void loadAndEnableAllNetworks() {
+ if (DBG) log("Loading config and enabling all networks");
+ loadConfiguredNetworks();
+ enableAllNetworks();
+ }
+
+ /**
+ * Fetch the list of currently configured networks
+ * @return List of networks
+ */
+ List<WifiConfiguration> getConfiguredNetworks() {
+ List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ networks.add(new WifiConfiguration(config));
+ }
+ return networks;
+ }
+
+ /**
+ * enable all networks and save config. This will be a no-op if the list
+ * of configured networks indicates all networks as being enabled
+ */
+ void enableAllNetworks() {
+ boolean networkEnabledStateChanged = false;
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.status == Status.DISABLED) {
+ if(mWifiNative.enableNetwork(config.networkId, false)) {
+ networkEnabledStateChanged = true;
+ config.status = Status.ENABLED;
+ } else {
+ loge("Enable network failed on " + config.networkId);
+ }
+ }
+ }
+
+ if (networkEnabledStateChanged) {
+ mWifiNative.saveConfig();
+ sendConfiguredNetworksChangedBroadcast();
+ }
+ }
+
+
+ /**
+ * Selects the specified network for connection. This involves
+ * updating the priority of all the networks and enabling the given
+ * network while disabling others.
+ *
+ * Selecting a network will leave the other networks disabled and
+ * a call to enableAllNetworks() needs to be issued upon a connection
+ * or a failure event from supplicant
+ *
+ * @param netId network to select for connection
+ * @return false if the network id is invalid
+ */
+ boolean selectNetwork(int netId) {
+ if (VDBG) localLog("selectNetwork", netId);
+ if (netId == INVALID_NETWORK_ID) return false;
+
+ // Reset the priority of each network at start or if it goes too high.
+ if (mLastPriority == -1 || mLastPriority > 1000000) {
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if (config.networkId != INVALID_NETWORK_ID) {
+ config.priority = 0;
+ addOrUpdateNetworkNative(config);
+ }
+ }
+ mLastPriority = 0;
+ }
+
+ // Set to the highest priority and save the configuration.
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = netId;
+ config.priority = ++mLastPriority;
+
+ addOrUpdateNetworkNative(config);
+ mWifiNative.saveConfig();
+
+ /* Enable the given network while disabling all other networks */
+ enableNetworkWithoutBroadcast(netId, true);
+
+ /* Avoid saving the config & sending a broadcast to prevent settings
+ * from displaying a disabled list of networks */
+ return true;
+ }
+
+ /**
+ * Add/update the specified configuration and save config
+ *
+ * @param config WifiConfiguration to be saved
+ * @return network update result
+ */
+ NetworkUpdateResult saveNetwork(WifiConfiguration config) {
+ if (VDBG) localLog("saveNetwork", config.networkId);
+ // A new network cannot have null SSID
+ if (config == null || (config.networkId == INVALID_NETWORK_ID &&
+ config.SSID == null)) {
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+
+ boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
+ NetworkUpdateResult result = addOrUpdateNetworkNative(config);
+ int netId = result.getNetworkId();
+ /* enable a new network */
+ if (newNetwork && netId != INVALID_NETWORK_ID) {
+ mWifiNative.enableNetwork(netId, false);
+ mConfiguredNetworks.get(netId).status = Status.ENABLED;
+ }
+ mWifiNative.saveConfig();
+ sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
+ WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ return result;
+ }
+
+ void updateStatus(int netId, DetailedState state) {
+ if (netId != INVALID_NETWORK_ID) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config == null) return;
+ switch (state) {
+ case CONNECTED:
+ config.status = Status.CURRENT;
+ break;
+ case DISCONNECTED:
+ //If network is already disabled, keep the status
+ if (config.status == Status.CURRENT) {
+ config.status = Status.ENABLED;
+ }
+ break;
+ default:
+ //do nothing, retain the existing state
+ break;
+ }
+ }
+ }
+
+ /**
+ * Forget the specified network and save config
+ *
+ * @param netId network to forget
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean forgetNetwork(int netId) {
+ if (VDBG) localLog("forgetNetwork", netId);
+ if (mWifiNative.removeNetwork(netId)) {
+ mWifiNative.saveConfig();
+ removeConfigAndSendBroadcastIfNeeded(netId);
+ return true;
+ } else {
+ loge("Failed to remove network " + netId);
+ return false;
+ }
+ }
+
+ /**
+ * Add/update a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful saveNetwork() is used by the
+ * state machine
+ *
+ * @param config wifi configuration to add/update
+ * @return network Id
+ */
+ int addOrUpdateNetwork(WifiConfiguration config) {
+ if (VDBG) localLog("addOrUpdateNetwork", config.networkId);
+ NetworkUpdateResult result = addOrUpdateNetworkNative(config);
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ sendConfiguredNetworksChangedBroadcast(mConfiguredNetworks.get(result.getNetworkId()),
+ result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED :
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return result.getNetworkId();
+ }
+
+ /**
+ * Remove a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful forgetNetwork() is used by the
+ * state machine for network removal
+ *
+ * @param netId network to be removed
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean removeNetwork(int netId) {
+ if (VDBG) localLog("removeNetwork", netId);
+ boolean ret = mWifiNative.removeNetwork(netId);
+ if (ret) {
+ removeConfigAndSendBroadcastIfNeeded(netId);
+ }
+ return ret;
+ }
+
+ private void removeConfigAndSendBroadcastIfNeeded(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) {
+ // Remove any associated keys
+ if (config.enterpriseConfig != null) {
+ removeKeys(config.enterpriseConfig);
+ }
+ mConfiguredNetworks.remove(netId);
+ mNetworkIds.remove(configKey(config));
+
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+ }
+ }
+
+ /**
+ * Enable a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful selectNetwork()/saveNetwork() is used by the
+ * state machine for connecting to a network
+ *
+ * @param netId network to be enabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean enableNetwork(int netId, boolean disableOthers) {
+ boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
+ if (disableOthers) {
+ if (VDBG) localLog("enableNetwork(disableOthers=true) ", netId);
+ sendConfiguredNetworksChangedBroadcast();
+ } else {
+ if (VDBG) localLog("enableNetwork(disableOthers=false) ", netId);
+ WifiConfiguration enabledNetwork = null;
+ synchronized(mConfiguredNetworks) {
+ enabledNetwork = mConfiguredNetworks.get(netId);
+ }
+ // check just in case the network was removed by someone else.
+ if (enabledNetwork != null) {
+ sendConfiguredNetworksChangedBroadcast(enabledNetwork,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ }
+ return ret;
+ }
+
+ boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) {
+ boolean ret = mWifiNative.enableNetwork(netId, disableOthers);
+
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) config.status = Status.ENABLED;
+
+ if (disableOthers) {
+ markAllNetworksDisabledExcept(netId);
+ }
+ return ret;
+ }
+
+ void disableAllNetworks() {
+ if (VDBG) localLog("disableAllNetworks");
+ boolean networkDisabled = false;
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.status != Status.DISABLED) {
+ if(mWifiNative.disableNetwork(config.networkId)) {
+ networkDisabled = true;
+ config.status = Status.DISABLED;
+ } else {
+ loge("Disable network failed on " + config.networkId);
+ }
+ }
+ }
+
+ if (networkDisabled) {
+ sendConfiguredNetworksChangedBroadcast();
+ }
+ }
+ /**
+ * Disable a network. Note that there is no saveConfig operation.
+ * @param netId network to be disabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean disableNetwork(int netId) {
+ return disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON);
+ }
+
+ /**
+ * Disable a network. Note that there is no saveConfig operation.
+ * @param netId network to be disabled
+ * @param reason reason code network was disabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean disableNetwork(int netId, int reason) {
+ if (VDBG) localLog("disableNetwork", netId);
+ boolean ret = mWifiNative.disableNetwork(netId);
+ WifiConfiguration network = null;
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ /* Only change the reason if the network was not previously disabled */
+ if (config != null && config.status != Status.DISABLED) {
+ config.status = Status.DISABLED;
+ config.disableReason = reason;
+ network = config;
+ }
+ if (network != null) {
+ sendConfiguredNetworksChangedBroadcast(network,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return ret;
+ }
+
+ /**
+ * Save the configured networks in supplicant to disk
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean saveConfig() {
+ return mWifiNative.saveConfig();
+ }
+
+ /**
+ * Start WPS pin method configuration with pin obtained
+ * from the access point
+ * @param config WPS configuration
+ * @return Wps result containing status and pin
+ */
+ WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
+ /* WPS leaves all networks disabled */
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS pin method configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Start WPS pin method configuration with pin obtained
+ * from the device
+ * @return WpsResult indicating status and pin
+ */
+ WpsResult startWpsWithPinFromDevice(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
+ /* WPS leaves all networks disabled */
+ if (!TextUtils.isEmpty(result.pin)) {
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS pin method configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Start WPS push button configuration
+ * @param config WPS configuration
+ * @return WpsResult indicating status and pin
+ */
+ WpsResult startWpsPbc(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ if (mWifiNative.startWpsPbc(config.BSSID)) {
+ /* WPS leaves all networks disabled */
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS push button configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Fetch the link properties for a given network id
+ * @return LinkProperties for the given network id
+ */
+ LinkProperties getLinkProperties(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) return new LinkProperties(config.linkProperties);
+ return null;
+ }
+
+ /**
+ * set IP configuration for a given network id
+ */
+ void setLinkProperties(int netId, LinkProperties linkProperties) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) {
+ // add old proxy details - TODO - is this still needed?
+ if(config.linkProperties != null) {
+ linkProperties.setHttpProxy(config.linkProperties.getHttpProxy());
+ }
+ config.linkProperties = linkProperties;
+ }
+ }
+
+ /**
+ * clear IP configuration for a given network id
+ * @param network id
+ */
+ void clearLinkProperties(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null && config.linkProperties != null) {
+ // Clear everything except proxy
+ ProxyProperties proxy = config.linkProperties.getHttpProxy();
+ config.linkProperties.clear();
+ config.linkProperties.setHttpProxy(proxy);
+ }
+ }
+
+
+ /**
+ * Fetch the proxy properties for a given network id
+ * @param network id
+ * @return ProxyProperties for the network id
+ */
+ ProxyProperties getProxyProperties(int netId) {
+ LinkProperties linkProperties = getLinkProperties(netId);
+ if (linkProperties != null) {
+ return new ProxyProperties(linkProperties.getHttpProxy());
+ }
+ return null;
+ }
+
+ /**
+ * Return if the specified network is using static IP
+ * @param network id
+ * @return {@code true} if using static ip for netId
+ */
+ boolean isUsingStaticIp(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null && config.ipAssignment == IpAssignment.STATIC) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Should be called when a single network configuration is made.
+ * @param network The network configuration that changed.
+ * @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
+ * WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
+ */
+ private void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network,
+ int reason) {
+ Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+ intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
+ intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Should be called when multiple network configuration changes are made.
+ */
+ private void sendConfiguredNetworksChangedBroadcast() {
+ Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ void loadConfiguredNetworks() {
+ String listStr = mWifiNative.listNetworks();
+ mLastPriority = 0;
+
+ mConfiguredNetworks.clear();
+ mNetworkIds.clear();
+
+ if (listStr == null)
+ return;
+
+ String[] lines = listStr.split("\n");
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ // network-id | ssid | bssid | flags
+ WifiConfiguration config = new WifiConfiguration();
+ try {
+ config.networkId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ loge("Failed to read network-id '" + result[0] + "'");
+ continue;
+ }
+ if (result.length > 3) {
+ if (result[3].indexOf("[CURRENT]") != -1)
+ config.status = WifiConfiguration.Status.CURRENT;
+ else if (result[3].indexOf("[DISABLED]") != -1)
+ config.status = WifiConfiguration.Status.DISABLED;
+ else
+ config.status = WifiConfiguration.Status.ENABLED;
+ } else {
+ config.status = WifiConfiguration.Status.ENABLED;
+ }
+ readNetworkVariables(config);
+ if (config.priority > mLastPriority) {
+ mLastPriority = config.priority;
+ }
+ config.ipAssignment = IpAssignment.DHCP;
+ config.proxySettings = ProxySettings.NONE;
+
+ if (mNetworkIds.containsKey(configKey(config))) {
+ // That SSID is already known, just ignore this duplicate entry
+ if (VDBG) localLog("discarded duplicate network", config.networkId);
+ } else {
+ mConfiguredNetworks.put(config.networkId, config);
+ mNetworkIds.put(configKey(config), config.networkId);
+ if (VDBG) localLog("loaded configured network", config.networkId);
+ }
+ }
+
+ readIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast();
+
+ if (VDBG) localLog("loadConfiguredNetworks loaded " + mNetworkIds.size() + " networks");
+
+ if (mNetworkIds.size() == 0) {
+ // no networks? Lets log if the wpa_supplicant.conf file contents
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
+ if (VDBG) localLog("--- Begin wpa_supplicant.conf Contents ---");
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ if (VDBG) localLog(line);
+ }
+ if (VDBG) localLog("--- End wpa_supplicant.conf Contents ---");
+ } catch (FileNotFoundException e) {
+ if (VDBG) localLog("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
+ } catch (IOException e) {
+ if (VDBG) localLog("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ // Just ignore the fact that we couldn't close
+ }
+ }
+ }
+ }
+
+ /* Mark all networks except specified netId as disabled */
+ private void markAllNetworksDisabledExcept(int netId) {
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.networkId != netId) {
+ if (config.status != Status.DISABLED) {
+ config.status = Status.DISABLED;
+ config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON;
+ }
+ }
+ }
+ }
+
+ private void markAllNetworksDisabled() {
+ markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
+ }
+
+ boolean needsUnlockedKeyStore() {
+
+ // Any network using certificates to authenticate access requires
+ // unlocked key store; unless the certificates can be stored with
+ // hardware encryption
+
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+ && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+
+ if (needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void writeIpAndProxyConfigurations() {
+
+ /* Make a copy */
+ List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ networks.add(new WifiConfiguration(config));
+ }
+
+ DelayedDiskWrite.write(networks);
+ }
+
+ private static class DelayedDiskWrite {
+
+ private static HandlerThread sDiskWriteHandlerThread;
+ private static Handler sDiskWriteHandler;
+ /* Tracks multiple writes on the same thread */
+ private static int sWriteSequence = 0;
+ private static final String TAG = "DelayedDiskWrite";
+
+ static void write (final List<WifiConfiguration> networks) {
+
+ /* Do a delayed write to disk on a seperate handler thread */
+ synchronized (DelayedDiskWrite.class) {
+ if (++sWriteSequence == 1) {
+ sDiskWriteHandlerThread = new HandlerThread("WifiConfigThread");
+ sDiskWriteHandlerThread.start();
+ sDiskWriteHandler = new Handler(sDiskWriteHandlerThread.getLooper());
+ }
+ }
+
+ sDiskWriteHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onWriteCalled(networks);
+ }
+ });
+ }
+
+ private static void onWriteCalled(List<WifiConfiguration> networks) {
+
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(ipConfigFile)));
+
+ out.writeInt(IPCONFIG_FILE_VERSION);
+
+ for(WifiConfiguration config : networks) {
+ boolean writeToFile = false;
+
+ try {
+ LinkProperties linkProperties = config.linkProperties;
+ switch (config.ipAssignment) {
+ case STATIC:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.ipAssignment.toString());
+ for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) {
+ out.writeUTF(LINK_ADDRESS_KEY);
+ out.writeUTF(linkAddr.getAddress().getHostAddress());
+ out.writeInt(linkAddr.getNetworkPrefixLength());
+ }
+ for (RouteInfo route : linkProperties.getRoutes()) {
+ out.writeUTF(GATEWAY_KEY);
+ LinkAddress dest = route.getDestination();
+ if (dest != null) {
+ out.writeInt(1);
+ out.writeUTF(dest.getAddress().getHostAddress());
+ out.writeInt(dest.getNetworkPrefixLength());
+ } else {
+ out.writeInt(0);
+ }
+ if (route.getGateway() != null) {
+ out.writeInt(1);
+ out.writeUTF(route.getGateway().getHostAddress());
+ } else {
+ out.writeInt(0);
+ }
+ }
+ for (InetAddress inetAddr : linkProperties.getDnses()) {
+ out.writeUTF(DNS_KEY);
+ out.writeUTF(inetAddr.getHostAddress());
+ }
+ writeToFile = true;
+ break;
+ case DHCP:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.ipAssignment.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid ip assignment while writing");
+ break;
+ }
+
+ switch (config.proxySettings) {
+ case STATIC:
+ ProxyProperties proxyProperties = linkProperties.getHttpProxy();
+ String exclusionList = proxyProperties.getExclusionList();
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(PROXY_HOST_KEY);
+ out.writeUTF(proxyProperties.getHost());
+ out.writeUTF(PROXY_PORT_KEY);
+ out.writeInt(proxyProperties.getPort());
+ out.writeUTF(EXCLUSION_LIST_KEY);
+ out.writeUTF(exclusionList);
+ writeToFile = true;
+ break;
+ case PAC:
+ ProxyProperties proxyPacProperties = linkProperties.getHttpProxy();
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(PROXY_PAC_FILE);
+ out.writeUTF(proxyPacProperties.getPacFileUrl());
+ writeToFile = true;
+ break;
+ case NONE:
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignthisore invalid proxy settings while writing");
+ break;
+ }
+ if (writeToFile) {
+ out.writeUTF(ID_KEY);
+ out.writeInt(configKey(config));
+ }
+ } catch (NullPointerException e) {
+ loge("Failure in writing " + config.linkProperties + e);
+ }
+ out.writeUTF(EOS);
+ }
+
+ } catch (IOException e) {
+ loge("Error writing data file");
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) {}
+ }
+
+ //Quit if no more writes sent
+ synchronized (DelayedDiskWrite.class) {
+ if (--sWriteSequence == 0) {
+ sDiskWriteHandler.getLooper().quit();
+ sDiskWriteHandler = null;
+ sDiskWriteHandlerThread = null;
+ }
+ }
+ }
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+ }
+
+ private void readIpAndProxyConfigurations() {
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ ipConfigFile)));
+
+ int version = in.readInt();
+ if (version != 2 && version != 1) {
+ loge("Bad version on IP configuration file, ignore read");
+ return;
+ }
+
+ while (true) {
+ int id = -1;
+ // Default is DHCP with no proxy
+ IpAssignment ipAssignment = IpAssignment.DHCP;
+ ProxySettings proxySettings = ProxySettings.NONE;
+ LinkProperties linkProperties = new LinkProperties();
+ String proxyHost = null;
+ String pacFileUrl = null;
+ int proxyPort = -1;
+ String exclusionList = null;
+ String key;
+
+ do {
+ key = in.readUTF();
+ try {
+ if (key.equals(ID_KEY)) {
+ id = in.readInt();
+ } else if (key.equals(IP_ASSIGNMENT_KEY)) {
+ ipAssignment = IpAssignment.valueOf(in.readUTF());
+ } else if (key.equals(LINK_ADDRESS_KEY)) {
+ LinkAddress linkAddr = new LinkAddress(
+ NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
+ linkProperties.addLinkAddress(linkAddr);
+ } else if (key.equals(GATEWAY_KEY)) {
+ LinkAddress dest = null;
+ InetAddress gateway = null;
+ if (version == 1) {
+ // only supported default gateways - leave the dest/prefix empty
+ gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+ } else {
+ if (in.readInt() == 1) {
+ dest = new LinkAddress(
+ NetworkUtils.numericToInetAddress(in.readUTF()),
+ in.readInt());
+ }
+ if (in.readInt() == 1) {
+ gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+ }
+ }
+ linkProperties.addRoute(new RouteInfo(dest, gateway));
+ } else if (key.equals(DNS_KEY)) {
+ linkProperties.addDns(
+ NetworkUtils.numericToInetAddress(in.readUTF()));
+ } else if (key.equals(PROXY_SETTINGS_KEY)) {
+ proxySettings = ProxySettings.valueOf(in.readUTF());
+ } else if (key.equals(PROXY_HOST_KEY)) {
+ proxyHost = in.readUTF();
+ } else if (key.equals(PROXY_PORT_KEY)) {
+ proxyPort = in.readInt();
+ } else if (key.equals(PROXY_PAC_FILE)) {
+ pacFileUrl = in.readUTF();
+ } else if (key.equals(EXCLUSION_LIST_KEY)) {
+ exclusionList = in.readUTF();
+ } else if (key.equals(EOS)) {
+ break;
+ } else {
+ loge("Ignore unknown key " + key + "while reading");
+ }
+ } catch (IllegalArgumentException e) {
+ loge("Ignore invalid address while reading" + e);
+ }
+ } while (true);
+
+ if (id != -1) {
+ WifiConfiguration config = mConfiguredNetworks.get(
+ mNetworkIds.get(id));
+
+ if (config == null) {
+ loge("configuration found for missing network, ignored");
+ } else {
+ config.linkProperties = linkProperties;
+ switch (ipAssignment) {
+ case STATIC:
+ case DHCP:
+ config.ipAssignment = ipAssignment;
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED IP on file, use DHCP");
+ config.ipAssignment = IpAssignment.DHCP;
+ break;
+ default:
+ loge("Ignore invalid ip assignment while reading");
+ break;
+ }
+
+ switch (proxySettings) {
+ case STATIC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyProperties =
+ new ProxyProperties(proxyHost, proxyPort, exclusionList);
+ linkProperties.setHttpProxy(proxyProperties);
+ break;
+ case PAC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyPacProperties =
+ new ProxyProperties(pacFileUrl);
+ linkProperties.setHttpProxy(proxyPacProperties);
+ break;
+ case NONE:
+ config.proxySettings = proxySettings;
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED proxy on file, use NONE");
+ config.proxySettings = ProxySettings.NONE;
+ break;
+ default:
+ loge("Ignore invalid proxy settings while reading");
+ break;
+ }
+ }
+ } else {
+ if (DBG) log("Missing id while parsing configuration");
+ }
+ }
+ } catch (EOFException ignore) {
+ } catch (IOException e) {
+ loge("Error parsing configuration" + e);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Exception e) {}
+ }
+ }
+ }
+
+ private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) {
+ /*
+ * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
+ * network configuration. Otherwise, the networkId should
+ * refer to an existing configuration.
+ */
+
+ if (VDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+
+ int netId = config.networkId;
+ boolean newNetwork = false;
+ // networkId of INVALID_NETWORK_ID means we want to create a new network
+ if (netId == INVALID_NETWORK_ID) {
+ Integer savedNetId = mNetworkIds.get(configKey(config));
+ if (savedNetId != null) {
+ netId = savedNetId;
+ } else {
+ newNetwork = true;
+ netId = mWifiNative.addNetwork();
+ if (netId < 0) {
+ loge("Failed to add a network!");
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+ }
+ }
+
+ boolean updateFailed = true;
+
+ setVariables: {
+
+ if (config.SSID != null &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.ssidVarName,
+ config.SSID)) {
+ loge("failed to set SSID: "+config.SSID);
+ break setVariables;
+ }
+
+ if (config.BSSID != null &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.bssidVarName,
+ config.BSSID)) {
+ loge("failed to set BSSID: "+config.BSSID);
+ break setVariables;
+ }
+
+ String allowedKeyManagementString =
+ makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
+ if (config.allowedKeyManagement.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.KeyMgmt.varName,
+ allowedKeyManagementString)) {
+ loge("failed to set key_mgmt: "+
+ allowedKeyManagementString);
+ break setVariables;
+ }
+
+ String allowedProtocolsString =
+ makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
+ if (config.allowedProtocols.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.Protocol.varName,
+ allowedProtocolsString)) {
+ loge("failed to set proto: "+
+ allowedProtocolsString);
+ break setVariables;
+ }
+
+ String allowedAuthAlgorithmsString =
+ makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
+ if (config.allowedAuthAlgorithms.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.AuthAlgorithm.varName,
+ allowedAuthAlgorithmsString)) {
+ loge("failed to set auth_alg: "+
+ allowedAuthAlgorithmsString);
+ break setVariables;
+ }
+
+ String allowedPairwiseCiphersString =
+ makeString(config.allowedPairwiseCiphers,
+ WifiConfiguration.PairwiseCipher.strings);
+ if (config.allowedPairwiseCiphers.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.PairwiseCipher.varName,
+ allowedPairwiseCiphersString)) {
+ loge("failed to set pairwise: "+
+ allowedPairwiseCiphersString);
+ break setVariables;
+ }
+
+ String allowedGroupCiphersString =
+ makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
+ if (config.allowedGroupCiphers.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.GroupCipher.varName,
+ allowedGroupCiphersString)) {
+ loge("failed to set group: "+
+ allowedGroupCiphersString);
+ break setVariables;
+ }
+
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.pskVarName,
+ config.preSharedKey)) {
+ loge("failed to set psk");
+ break setVariables;
+ }
+
+ boolean hasSetKey = false;
+ if (config.wepKeys != null) {
+ for (int i = 0; i < config.wepKeys.length; i++) {
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.wepKeyVarNames[i],
+ config.wepKeys[i])) {
+ loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
+ break setVariables;
+ }
+ hasSetKey = true;
+ }
+ }
+ }
+
+ if (hasSetKey) {
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.wepTxKeyIdxVarName,
+ Integer.toString(config.wepTxKeyIndex))) {
+ loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
+ break setVariables;
+ }
+ }
+
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.priorityVarName,
+ Integer.toString(config.priority))) {
+ loge(config.SSID + ": failed to set priority: "
+ +config.priority);
+ break setVariables;
+ }
+
+ if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.hiddenSSIDVarName,
+ Integer.toString(config.hiddenSSID ? 1 : 0))) {
+ loge(config.SSID + ": failed to set hiddenSSID: "+
+ config.hiddenSSID);
+ break setVariables;
+ }
+
+ if (config.enterpriseConfig != null &&
+ config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+
+ WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+ if (needsKeyStore(enterpriseConfig)) {
+ /**
+ * Keyguard settings may eventually be controlled by device policy.
+ * We check here if keystore is unlocked before installing
+ * credentials.
+ * TODO: Do we need a dialog here ?
+ */
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ loge(config.SSID + ": key store is locked");
+ break setVariables;
+ }
+
+ try {
+ /* config passed may include only fields being updated.
+ * In order to generate the key id, fetch uninitialized
+ * fields from the currently tracked configuration
+ */
+ WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+ String keyId = config.getKeyIdForCredentials(currentConfig);
+
+ if (!installKeys(enterpriseConfig, keyId)) {
+ loge(config.SSID + ": failed to install keys");
+ break setVariables;
+ }
+ } catch (IllegalStateException e) {
+ loge(config.SSID + " invalid config for key installation");
+ break setVariables;
+ }
+ }
+
+ HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
+ for (String key : enterpriseFields.keySet()) {
+ String value = enterpriseFields.get(key);
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ key,
+ value)) {
+ removeKeys(enterpriseConfig);
+ loge(config.SSID + ": failed to set " + key +
+ ": " + value);
+ break setVariables;
+ }
+ }
+ }
+ updateFailed = false;
+ } //end of setVariables
+
+ if (updateFailed) {
+ if (newNetwork) {
+ mWifiNative.removeNetwork(netId);
+ loge("Failed to set a network variable, removed network: " + netId);
+ }
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+
+ /* An update of the network variables requires reading them
+ * back from the supplicant to update mConfiguredNetworks.
+ * This is because some of the variables (SSID, wep keys &
+ * passphrases) reflect different values when read back than
+ * when written. For example, wep key is stored as * irrespective
+ * of the value sent to the supplicant
+ */
+ WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+ if (currentConfig == null) {
+ currentConfig = new WifiConfiguration();
+ currentConfig.ipAssignment = IpAssignment.DHCP;
+ currentConfig.proxySettings = ProxySettings.NONE;
+ currentConfig.networkId = netId;
+ }
+
+ readNetworkVariables(currentConfig);
+
+ mConfiguredNetworks.put(netId, currentConfig);
+ mNetworkIds.put(configKey(currentConfig), netId);
+
+ NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
+ result.setIsNewNetwork(newNetwork);
+ result.setNetworkId(netId);
+ return result;
+ }
+
+ /* Compare current and new configuration and write to file on change */
+ private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
+ WifiConfiguration currentConfig,
+ WifiConfiguration newConfig) {
+ boolean ipChanged = false;
+ boolean proxyChanged = false;
+ LinkProperties linkProperties = null;
+
+ switch (newConfig.ipAssignment) {
+ case STATIC:
+ Collection<LinkAddress> currentLinkAddresses = currentConfig.linkProperties
+ .getLinkAddresses();
+ Collection<LinkAddress> newLinkAddresses = newConfig.linkProperties
+ .getLinkAddresses();
+ Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
+ Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
+ Collection<RouteInfo> currentRoutes = currentConfig.linkProperties.getRoutes();
+ Collection<RouteInfo> newRoutes = newConfig.linkProperties.getRoutes();
+
+ boolean linkAddressesDiffer =
+ (currentLinkAddresses.size() != newLinkAddresses.size()) ||
+ !currentLinkAddresses.containsAll(newLinkAddresses);
+ boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
+ !currentDnses.containsAll(newDnses);
+ boolean routesDiffer = (currentRoutes.size() != newRoutes.size()) ||
+ !currentRoutes.containsAll(newRoutes);
+
+ if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
+ linkAddressesDiffer ||
+ dnsesDiffer ||
+ routesDiffer) {
+ ipChanged = true;
+ }
+ break;
+ case DHCP:
+ if (currentConfig.ipAssignment != newConfig.ipAssignment) {
+ ipChanged = true;
+ }
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid ip assignment during write");
+ break;
+ }
+
+ switch (newConfig.proxySettings) {
+ case STATIC:
+ case PAC:
+ ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy();
+ ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy();
+
+ if (newHttpProxy != null) {
+ proxyChanged = !newHttpProxy.equals(currentHttpProxy);
+ } else {
+ proxyChanged = (currentHttpProxy != null);
+ }
+ break;
+ case NONE:
+ if (currentConfig.proxySettings != newConfig.proxySettings) {
+ proxyChanged = true;
+ }
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid proxy configuration during write");
+ break;
+ }
+
+ if (!ipChanged) {
+ linkProperties = copyIpSettingsFromConfig(currentConfig);
+ } else {
+ currentConfig.ipAssignment = newConfig.ipAssignment;
+ linkProperties = copyIpSettingsFromConfig(newConfig);
+ log("IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
+ linkProperties.toString());
+ }
+
+
+ if (!proxyChanged) {
+ linkProperties.setHttpProxy(currentConfig.linkProperties.getHttpProxy());
+ } else {
+ currentConfig.proxySettings = newConfig.proxySettings;
+ linkProperties.setHttpProxy(newConfig.linkProperties.getHttpProxy());
+ log("proxy changed SSID = " + currentConfig.SSID);
+ if (linkProperties.getHttpProxy() != null) {
+ log(" proxyProperties: " + linkProperties.getHttpProxy().toString());
+ }
+ }
+
+ if (ipChanged || proxyChanged) {
+ currentConfig.linkProperties = linkProperties;
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(currentConfig,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return new NetworkUpdateResult(ipChanged, proxyChanged);
+ }
+
+ private LinkProperties copyIpSettingsFromConfig(WifiConfiguration config) {
+ LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(config.linkProperties.getInterfaceName());
+ for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
+ linkProperties.addLinkAddress(linkAddr);
+ }
+ for (RouteInfo route : config.linkProperties.getRoutes()) {
+ linkProperties.addRoute(route);
+ }
+ for (InetAddress dns : config.linkProperties.getDnses()) {
+ linkProperties.addDns(dns);
+ }
+ return linkProperties;
+ }
+
+ /**
+ * Read the variables from the supplicant daemon that are needed to
+ * fill in the WifiConfiguration object.
+ *
+ * @param config the {@link WifiConfiguration} object to be filled in.
+ */
+ private void readNetworkVariables(WifiConfiguration config) {
+
+ int netId = config.networkId;
+ if (netId < 0)
+ return;
+
+ /*
+ * TODO: maybe should have a native method that takes an array of
+ * variable names and returns an array of values. But we'd still
+ * be doing a round trip to the supplicant daemon for each variable.
+ */
+ String value;
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ if (value.charAt(0) != '"') {
+ config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
+ //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
+ //supplicant string
+ } else {
+ config.SSID = value;
+ }
+ } else {
+ config.SSID = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.BSSID = value;
+ } else {
+ config.BSSID = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
+ config.priority = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.priority = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
+ config.hiddenSSID = false;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.hiddenSSID = Integer.parseInt(value) != 0;
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
+ config.wepTxKeyIndex = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.wepTxKeyIndex = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ for (int i = 0; i < 4; i++) {
+ value = mWifiNative.getNetworkVariable(netId,
+ WifiConfiguration.wepKeyVarNames[i]);
+ if (!TextUtils.isEmpty(value)) {
+ config.wepKeys[i] = value;
+ } else {
+ config.wepKeys[i] = null;
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.preSharedKey = value;
+ } else {
+ config.preSharedKey = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.Protocol.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.Protocol.strings);
+ if (0 <= index) {
+ config.allowedProtocols.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.KeyMgmt.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.KeyMgmt.strings);
+ if (0 <= index) {
+ config.allowedKeyManagement.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.AuthAlgorithm.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
+ if (0 <= index) {
+ config.allowedAuthAlgorithms.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.PairwiseCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.PairwiseCipher.strings);
+ if (0 <= index) {
+ config.allowedPairwiseCiphers.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.GroupCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.GroupCipher.strings);
+ if (0 <= index) {
+ config.allowedGroupCiphers.set(index);
+ }
+ }
+ }
+
+ if (config.enterpriseConfig == null) {
+ config.enterpriseConfig = new WifiEnterpriseConfig();
+ }
+ HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
+ for (String key : ENTERPRISE_CONFIG_SUPPLICANT_KEYS) {
+ value = mWifiNative.getNetworkVariable(netId, key);
+ if (!TextUtils.isEmpty(value)) {
+ enterpriseFields.put(key, removeDoubleQuotes(value));
+ } else {
+ enterpriseFields.put(key, EMPTY_VALUE);
+ }
+ }
+
+ if (migrateOldEapTlsNative(config.enterpriseConfig, netId)) {
+ saveConfig();
+ }
+
+ migrateCerts(config.enterpriseConfig);
+ // initializeSoftwareKeystoreFlag(config.enterpriseConfig, mKeyStore);
+ }
+
+ private static String removeDoubleQuotes(String string) {
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ private static String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
+ private static String makeString(BitSet set, String[] strings) {
+ StringBuffer buf = new StringBuffer();
+ int nextSetBit = -1;
+
+ /* Make sure all set bits are in [0, strings.length) to avoid
+ * going out of bounds on strings. (Shouldn't happen, but...) */
+ set = set.get(0, strings.length);
+
+ while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+ buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+ }
+
+ // remove trailing space
+ if (set.cardinality() > 0) {
+ buf.setLength(buf.length() - 1);
+ }
+
+ return buf.toString();
+ }
+
+ private int lookupString(String string, String[] strings) {
+ int size = strings.length;
+
+ string = string.replace('-', '_');
+
+ for (int i = 0; i < size; i++)
+ if (string.equals(strings[i]))
+ return i;
+
+ // if we ever get here, we should probably add the
+ // value to WifiConfiguration to reflect that it's
+ // supported by the WPA supplicant
+ loge("Failed to look-up a string: " + string);
+
+ return -1;
+ }
+
+ /* Returns a unique for a given configuration */
+ private static int configKey(WifiConfiguration config) {
+ String key;
+
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
+ } else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
+ config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ } else if (config.wepKeys[0] != null) {
+ key = config.SSID + "WEP";
+ } else {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
+ }
+
+ return key.hashCode();
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("WifiConfigStore");
+ pw.println("mLastPriority " + mLastPriority);
+ pw.println("Configured networks");
+ for (WifiConfiguration conf : getConfiguredNetworks()) {
+ pw.println(conf);
+ }
+ pw.println();
+
+ if (mLocalLog != null) {
+ pw.println("WifiConfigStore - Log Begin ----");
+ mLocalLog.dump(fd, pw, args);
+ pw.println("WifiConfigStore - Log End ----");
+ }
+ }
+
+ public String getConfigFile() {
+ return ipConfigFile;
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, s);
+ }
+
+ private void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private void localLog(String s) {
+ if (mLocalLog != null) {
+ mLocalLog.log(s);
+ }
+ }
+
+ private void localLog(String s, int netId) {
+ if (mLocalLog == null) {
+ return;
+ }
+
+ WifiConfiguration config;
+ synchronized(mConfiguredNetworks) {
+ config = mConfiguredNetworks.get(netId);
+ }
+
+ if (config != null) {
+ mLocalLog.log(s + " " + config.getPrintableSsid());
+ } else {
+ mLocalLog.log(s + " " + netId);
+ }
+ }
+
+ // Certificate and private key management for EnterpriseConfig
+ static boolean needsKeyStore(WifiEnterpriseConfig config) {
+ // Has no keys to be installed
+ if (config.getClientCertificate() == null && config.getCaCertificate() == null)
+ return false;
+ return true;
+ }
+
+ static boolean isHardwareBackedKey(PrivateKey key) {
+ return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
+ }
+
+ static boolean hasHardwareBackedKey(Certificate certificate) {
+ return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
+ }
+
+ static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ if (!TextUtils.isEmpty(client)) {
+ // a valid client certificate is configured
+
+ // BUGBUG: keyStore.get() never returns certBytes; because it is not
+ // taking WIFI_UID as a parameter. It always looks for certificate
+ // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
+ // all certificates need software keystore until we get the get() API
+ // fixed.
+
+ return true;
+ }
+
+ /*
+ try {
+
+ if (DBG) Slog.d(TAG, "Loading client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ if (factory == null) {
+ Slog.e(TAG, "Error getting certificate factory");
+ return;
+ }
+
+ byte[] certBytes = keyStore.get(Credentials.USER_CERTIFICATE + client);
+ if (certBytes != null) {
+ Certificate cert = (X509Certificate) factory.generateCertificate(
+ new ByteArrayInputStream(certBytes));
+
+ if (cert != null) {
+ mNeedsSoftwareKeystore = hasHardwareBackedKey(cert);
+
+ if (DBG) Slog.d(TAG, "Loaded client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+ if (DBG) Slog.d(TAG, "It " + (mNeedsSoftwareKeystore ? "needs" :
+ "does not need" ) + " software key store");
+ } else {
+ Slog.d(TAG, "could not generate certificate");
+ }
+ } else {
+ Slog.e(TAG, "Could not load client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+ mNeedsSoftwareKeystore = true;
+ }
+
+ } catch(CertificateException e) {
+ Slog.e(TAG, "Could not read certificates");
+ mCaCert = null;
+ mClientCertificate = null;
+ }
+ */
+
+ return false;
+ }
+
+ boolean installKeys(WifiEnterpriseConfig config, String name) {
+ boolean ret = true;
+ String privKeyName = Credentials.USER_PRIVATE_KEY + name;
+ String userCertName = Credentials.USER_CERTIFICATE + name;
+ String caCertName = Credentials.CA_CERTIFICATE + name;
+ if (config.getClientCertificate() != null) {
+ byte[] privKeyData = config.getClientPrivateKey().getEncoded();
+ if (isHardwareBackedKey(config.getClientPrivateKey())) {
+ // Hardware backed key store is secure enough to store keys un-encrypted, this
+ // removes the need for user to punch a PIN to get access to these keys
+ if (DBG) Log.d(TAG, "importing keys " + name + " in hardware backed store");
+ ret = mKeyStore.importKey(privKeyName, privKeyData, android.os.Process.WIFI_UID,
+ KeyStore.FLAG_NONE);
+ } else {
+ // Software backed key store is NOT secure enough to store keys un-encrypted.
+ // Save keys encrypted so they are protected with user's PIN. User will
+ // have to unlock phone before being able to use these keys and connect to
+ // networks.
+ if (DBG) Log.d(TAG, "importing keys " + name + " in software backed store");
+ ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+ KeyStore.FLAG_ENCRYPTED);
+ }
+ if (ret == false) {
+ return ret;
+ }
+
+ ret = putCertInKeyStore(userCertName, config.getClientCertificate());
+ if (ret == false) {
+ // Remove private key installed
+ mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+ return ret;
+ }
+ }
+
+ if (config.getCaCertificate() != null) {
+ ret = putCertInKeyStore(caCertName, config.getCaCertificate());
+ if (ret == false) {
+ if (config.getClientCertificate() != null) {
+ // Remove client key+cert
+ mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+ mKeyStore.delete(userCertName, Process.WIFI_UID);
+ }
+ return ret;
+ }
+ }
+
+ // Set alias names
+ if (config.getClientCertificate() != null) {
+ config.setClientCertificateAlias(name);
+ config.resetClientKeyEntry();
+ }
+
+ if (config.getCaCertificate() != null) {
+ config.setCaCertificateAlias(name);
+ config.resetCaCertificate();
+ }
+
+ return ret;
+ }
+
+ private boolean putCertInKeyStore(String name, Certificate cert) {
+ try {
+ byte[] certData = Credentials.convertToPem(cert);
+ if (DBG) Log.d(TAG, "putting certificate " + name + " in keystore");
+ return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+
+ } catch (IOException e1) {
+ return false;
+ } catch (CertificateException e2) {
+ return false;
+ }
+ }
+
+ void removeKeys(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ // a valid client certificate is configured
+ if (!TextUtils.isEmpty(client)) {
+ if (DBG) Log.d(TAG, "removing client private key and user cert");
+ mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+ mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
+ }
+
+ String ca = config.getCaCertificateAlias();
+ // a valid ca certificate is configured
+ if (!TextUtils.isEmpty(ca)) {
+ if (DBG) Log.d(TAG, "removing CA cert");
+ mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
+ }
+ }
+
+
+ /** Migrates the old style TLS config to the new config style. This should only be used
+ * when restoring an old wpa_supplicant.conf or upgrading from a previous
+ * platform version.
+ * @return true if the config was updated
+ * @hide
+ */
+ boolean migrateOldEapTlsNative(WifiEnterpriseConfig config, int netId) {
+ String oldPrivateKey = mWifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
+ /*
+ * If the old configuration value is not present, then there is nothing
+ * to do.
+ */
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ } else {
+ // Also ignore it if it's empty quotes.
+ oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ }
+ }
+
+ config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ENABLE);
+ config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY,
+ WifiEnterpriseConfig.ENGINE_ID_KEYSTORE);
+
+ /*
+ * The old key started with the keystore:// URI prefix, but we don't
+ * need that anymore. Trim it off if it exists.
+ */
+ final String keyName;
+ if (oldPrivateKey.startsWith(WifiEnterpriseConfig.KEYSTORE_URI)) {
+ keyName = new String(
+ oldPrivateKey.substring(WifiEnterpriseConfig.KEYSTORE_URI.length()));
+ } else {
+ keyName = oldPrivateKey;
+ }
+ config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, keyName);
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""));
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_ID_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""));
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""));
+
+ // Remove old private_key string so we don't run this again.
+ mWifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
+
+ return true;
+ }
+
+ /** Migrate certs from global pool to wifi UID if not already done */
+ void migrateCerts(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ // a valid client certificate is configured
+ if (!TextUtils.isEmpty(client)) {
+ if (!mKeyStore.contains(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID)) {
+ mKeyStore.duplicate(Credentials.USER_PRIVATE_KEY + client, -1,
+ Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+ mKeyStore.duplicate(Credentials.USER_CERTIFICATE + client, -1,
+ Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
+ }
+ }
+
+ String ca = config.getCaCertificateAlias();
+ // a valid ca certificate is configured
+ if (!TextUtils.isEmpty(ca)) {
+ if (!mKeyStore.contains(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID)) {
+ mKeyStore.duplicate(Credentials.CA_CERTIFICATE + ca, -1,
+ Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
+ }
+ }
+ }
+
+}
+
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
new file mode 100644
index 000000000..530e2c3ee
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2013 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.server.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiServiceImpl.LockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class WifiController extends StateMachine {
+ private static final String TAG = "WifiController";
+ private static final boolean DBG = false;
+ private Context mContext;
+ private boolean mScreenOff;
+ private boolean mDeviceIdle;
+ private int mPluggedType;
+ private int mStayAwakeConditions;
+ private long mIdleMillis;
+ private int mSleepPolicy;
+ private boolean mFirstUserSignOnSeen = false;
+
+ private AlarmManager mAlarmManager;
+ private PendingIntent mIdleIntent;
+ private static final int IDLE_REQUEST = 0;
+
+ /**
+ * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
+ * Settings.Global value is not present. This timeout value is chosen as
+ * the approximate point at which the battery drain caused by Wi-Fi
+ * being enabled but not active exceeds the battery drain caused by
+ * re-establishing a connection to the mobile data network.
+ */
+ private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
+
+ /**
+ * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}. This is the default value if a
+ * Settings.Global value is not present. This is the minimum time after wifi is disabled
+ * we'll act on an enable. Enable requests received before this delay will be deferred.
+ */
+ private static final long DEFAULT_REENABLE_DELAY_MS = 500;
+
+ // finding that delayed messages can sometimes be delivered earlier than expected
+ // probably rounding errors.. add a margin to prevent problems
+ private static final long DEFER_MARGIN_MS = 5;
+
+ NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+ private static final String ACTION_DEVICE_IDLE =
+ "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+ /* References to values tracked in WifiService */
+ final WifiStateMachine mWifiStateMachine;
+ final WifiSettingsStore mSettingsStore;
+ final LockList mLocks;
+
+ /**
+ * Temporary for computing UIDS that are responsible for starting WIFI.
+ * Protected by mWifiStateTracker lock.
+ */
+ private final WorkSource mTmpWorkSource = new WorkSource();
+
+ private long mReEnableDelayMillis;
+
+ private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
+
+ static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1;
+ static final int CMD_SCREEN_ON = BASE + 2;
+ static final int CMD_SCREEN_OFF = BASE + 3;
+ static final int CMD_BATTERY_CHANGED = BASE + 4;
+ static final int CMD_DEVICE_IDLE = BASE + 5;
+ static final int CMD_LOCKS_CHANGED = BASE + 6;
+ static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7;
+ static final int CMD_WIFI_TOGGLED = BASE + 8;
+ static final int CMD_AIRPLANE_TOGGLED = BASE + 9;
+ static final int CMD_SET_AP = BASE + 10;
+ static final int CMD_DEFERRED_TOGGLE = BASE + 11;
+ static final int CMD_USER_PRESENT = BASE + 12;
+
+ private DefaultState mDefaultState = new DefaultState();
+ private StaEnabledState mStaEnabledState = new StaEnabledState();
+ private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
+ private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
+ private ApEnabledState mApEnabledState = new ApEnabledState();
+ private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
+ private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
+ private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
+ private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
+ private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
+ private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
+ private EcmState mEcmState = new EcmState();
+
+ WifiController(Context context, WifiServiceImpl service, Looper looper) {
+ super(TAG, looper);
+ mContext = context;
+ mWifiStateMachine = service.mWifiStateMachine;
+ mSettingsStore = service.mSettingsStore;
+ mLocks = service.mLocks;
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+ mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+ addState(mDefaultState);
+ addState(mApStaDisabledState, mDefaultState);
+ addState(mStaEnabledState, mDefaultState);
+ addState(mDeviceActiveState, mStaEnabledState);
+ addState(mDeviceInactiveState, mStaEnabledState);
+ addState(mScanOnlyLockHeldState, mDeviceInactiveState);
+ addState(mFullLockHeldState, mDeviceInactiveState);
+ addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
+ addState(mNoLockHeldState, mDeviceInactiveState);
+ addState(mStaDisabledWithScanState, mDefaultState);
+ addState(mApEnabledState, mDefaultState);
+ addState(mEcmState, mDefaultState);
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ setInitialState(mStaDisabledWithScanState);
+ } else {
+ setInitialState(mApStaDisabledState);
+ }
+ setLogRecSize(100);
+ setLogOnlyTransitions(false);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DEVICE_IDLE);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_DEVICE_IDLE)) {
+ sendMessage(CMD_DEVICE_IDLE);
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ }
+ }
+ },
+ new IntentFilter(filter));
+
+ initializeAndRegisterForSettingsChange(looper);
+ }
+
+ private void initializeAndRegisterForSettingsChange(Looper looper) {
+ Handler handler = new Handler(looper);
+ readStayAwakeConditions();
+ registerForStayAwakeModeChange(handler);
+ readWifiIdleTime();
+ registerForWifiIdleTimeChange(handler);
+ readWifiSleepPolicy();
+ registerForWifiSleepPolicyChange(handler);
+ readWifiReEnableDelay();
+ }
+
+ private void readStayAwakeConditions() {
+ mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+ }
+
+ private void readWifiIdleTime() {
+ mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
+ }
+
+ private void readWifiSleepPolicy() {
+ mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SLEEP_POLICY,
+ Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+ }
+
+ private void readWifiReEnableDelay() {
+ mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
+ }
+
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForStayAwakeModeChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readStayAwakeConditions();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForWifiIdleTimeChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readWifiIdleTime();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes changes to wifi sleep policy
+ */
+ private void registerForWifiSleepPolicyChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readWifiSleepPolicy();
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
+ false, contentObserver);
+ }
+
+ /**
+ * Determines whether the Wi-Fi chipset should stay awake or be put to
+ * sleep. Looks at the setting for the sleep policy and the current
+ * conditions.
+ *
+ * @see #shouldDeviceStayAwake(int)
+ */
+ private boolean shouldWifiStayAwake(int pluggedType) {
+ if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
+ // Never sleep
+ return true;
+ } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+ (pluggedType != 0)) {
+ // Never sleep while plugged, and we're plugged
+ return true;
+ } else {
+ // Default
+ return shouldDeviceStayAwake(pluggedType);
+ }
+ }
+
+ /**
+ * Determine whether the bit value corresponding to {@code pluggedType} is set in
+ * the bit string mStayAwakeConditions. This determines whether the device should
+ * stay awake based on the current plugged type.
+ *
+ * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+ * being made
+ * @return {@code true} if {@code pluggedType} indicates that the device is
+ * supposed to stay awake, {@code false} otherwise.
+ */
+ private boolean shouldDeviceStayAwake(int pluggedType) {
+ return (mStayAwakeConditions & pluggedType) != 0;
+ }
+
+ private void updateBatteryWorkSource() {
+ mTmpWorkSource.clear();
+ if (mDeviceIdle) {
+ mLocks.updateWorkSource(mTmpWorkSource);
+ }
+ mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_SCREEN_ON:
+ mAlarmManager.cancel(mIdleIntent);
+ mScreenOff = false;
+ mDeviceIdle = false;
+ updateBatteryWorkSource();
+ break;
+ case CMD_SCREEN_OFF:
+ mScreenOff = true;
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND the "stay on while plugged in" setting doesn't match the
+ * current power conditions (i.e, not plugged in, plugged in to USB,
+ * or plugged in to AC).
+ */
+ if (!shouldWifiStayAwake(mPluggedType)) {
+ //Delayed shutdown if wifi is connected
+ if (mNetworkInfo.getDetailedState() ==
+ NetworkInfo.DetailedState.CONNECTED) {
+ if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + mIdleMillis, mIdleIntent);
+ } else {
+ sendMessage(CMD_DEVICE_IDLE);
+ }
+ }
+ break;
+ case CMD_DEVICE_IDLE:
+ mDeviceIdle = true;
+ updateBatteryWorkSource();
+ break;
+ case CMD_BATTERY_CHANGED:
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND we are transitioning from a state in which the device was supposed
+ * to stay awake to a state in which it is not supposed to stay awake.
+ * If "stay awake" state is not changing, we do nothing, to avoid resetting
+ * the already-set timer.
+ */
+ int pluggedType = msg.arg1;
+ if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
+ if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
+ !shouldWifiStayAwake(pluggedType)) {
+ long triggerTime = System.currentTimeMillis() + mIdleMillis;
+ if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+ }
+
+ mPluggedType = pluggedType;
+ break;
+ case CMD_SET_AP:
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ case CMD_LOCKS_CHANGED:
+ case CMD_WIFI_TOGGLED:
+ case CMD_AIRPLANE_TOGGLED:
+ case CMD_EMERGENCY_MODE_CHANGED:
+ break;
+ case CMD_USER_PRESENT:
+ mFirstUserSignOnSeen = true;
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ log("DEFERRED_TOGGLE ignored due to state change");
+ break;
+ default:
+ throw new RuntimeException("WifiController.handleMessage " + msg.what);
+ }
+ return HANDLED;
+ }
+
+ }
+
+ class ApStaDisabledState extends State {
+ private int mDeferredEnableSerialNumber = 0;
+ private boolean mHaveDeferredEnable = false;
+ private long mDisabledTimestamp;
+
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(false);
+ // Supplicant can't restart right away, so not the time we switched off
+ mDisabledTimestamp = SystemClock.elapsedRealtime();
+ mDeferredEnableSerialNumber++;
+ mHaveDeferredEnable = false;
+ }
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (doDeferEnable(msg)) {
+ if (mHaveDeferredEnable) {
+ // have 2 toggles now, inc serial number an ignore both
+ mDeferredEnableSerialNumber++;
+ }
+ mHaveDeferredEnable = !mHaveDeferredEnable;
+ break;
+ }
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ }
+ break;
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ }
+ break;
+ case CMD_SET_AP:
+ if (msg.arg1 == 1) {
+ mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+ true);
+ transitionTo(mApEnabledState);
+ }
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ if (msg.arg1 != mDeferredEnableSerialNumber) {
+ log("DEFERRED_TOGGLE ignored due to serial mismatch");
+ break;
+ }
+ log("DEFERRED_TOGGLE handled");
+ sendMessage((Message)(msg.obj));
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private boolean doDeferEnable(Message msg) {
+ long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+ if (delaySoFar >= mReEnableDelayMillis) {
+ return false;
+ }
+
+ log("WifiController msg " + msg + " deferred for " +
+ (mReEnableDelayMillis - delaySoFar) + "ms");
+
+ // need to defer this action.
+ Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+ deferredMsg.obj = Message.obtain(msg);
+ deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+ sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+ return true;
+ }
+
+ }
+
+ class StaEnabledState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(true);
+ }
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ if (! mSettingsStore.isWifiToggleEnabled()) {
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ } else {
+ transitionTo(mApStaDisabledState);
+ }
+ }
+ break;
+ case CMD_AIRPLANE_TOGGLED:
+ /* When wi-fi is turned off due to airplane,
+ * disable entirely (including scan)
+ */
+ if (! mSettingsStore.isWifiToggleEnabled()) {
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_EMERGENCY_MODE_CHANGED:
+ if (msg.arg1 == 1) {
+ transitionTo(mEcmState);
+ break;
+ }
+ default:
+ return NOT_HANDLED;
+
+ }
+ return HANDLED;
+ }
+ }
+
+ class StaDisabledWithScanState extends State {
+ private int mDeferredEnableSerialNumber = 0;
+ private boolean mHaveDeferredEnable = false;
+ private long mDisabledTimestamp;
+
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(true);
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ // Supplicant can't restart right away, so not the time we switched off
+ mDisabledTimestamp = SystemClock.elapsedRealtime();
+ mDeferredEnableSerialNumber++;
+ mHaveDeferredEnable = false;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (doDeferEnable(msg)) {
+ if (mHaveDeferredEnable) {
+ // have 2 toggles now, inc serial number and ignore both
+ mDeferredEnableSerialNumber++;
+ }
+ mHaveDeferredEnable = !mHaveDeferredEnable;
+ break;
+ }
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ }
+ break;
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isAirplaneModeOn() &&
+ ! mSettingsStore.isWifiToggleEnabled()) {
+ transitionTo(mApStaDisabledState);
+ }
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ if (! mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_SET_AP:
+ // Before starting tethering, turn off supplicant for scan mode
+ if (msg.arg1 == 1) {
+ deferMessage(msg);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ if (msg.arg1 != mDeferredEnableSerialNumber) {
+ log("DEFERRED_TOGGLE ignored due to serial mismatch");
+ break;
+ }
+ logd("DEFERRED_TOGGLE handled");
+ sendMessage((Message)(msg.obj));
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private boolean doDeferEnable(Message msg) {
+ long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+ if (delaySoFar >= mReEnableDelayMillis) {
+ return false;
+ }
+
+ log("WifiController msg " + msg + " deferred for " +
+ (mReEnableDelayMillis - delaySoFar) + "ms");
+
+ // need to defer this action.
+ Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+ deferredMsg.obj = Message.obtain(msg);
+ deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+ sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+ return true;
+ }
+
+ }
+
+ class ApEnabledState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isAirplaneModeOn()) {
+ mWifiStateMachine.setHostApRunning(null, false);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_SET_AP:
+ if (msg.arg1 == 0) {
+ mWifiStateMachine.setHostApRunning(null, false);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class EcmState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(false);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ } else if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ } else {
+ transitionTo(mApStaDisabledState);
+ }
+ return HANDLED;
+ } else {
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /* Parent: StaEnabledState */
+ class DeviceActiveState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(false);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_DEVICE_IDLE) {
+ checkLocksAndTransitionWhenDeviceIdle();
+ // We let default state handle the rest of work
+ } else if (msg.what == CMD_USER_PRESENT) {
+ // TLS networks can't connect until user unlocks keystore. KeyStore
+ // unlocks when the user punches PIN after the reboot. So use this
+ // trigger to get those networks connected.
+ if (mFirstUserSignOnSeen == false) {
+ mWifiStateMachine.reloadTlsNetworksAndReconnect();
+ }
+ mFirstUserSignOnSeen = true;
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /* Parent: StaEnabledState */
+ class DeviceInactiveState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_LOCKS_CHANGED:
+ checkLocksAndTransitionWhenDeviceIdle();
+ updateBatteryWorkSource();
+ return HANDLED;
+ case CMD_SCREEN_ON:
+ transitionTo(mDeviceActiveState);
+ // More work in default state
+ return NOT_HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
+ class ScanOnlyLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
+ class FullLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(false);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
+ class FullHighPerfLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(true);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
+ class NoLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setDriverStart(false);
+ }
+ }
+
+ private void checkLocksAndTransitionWhenDeviceIdle() {
+ if (mLocks.hasLocks()) {
+ switch (mLocks.getStrongestLockMode()) {
+ case WIFI_MODE_FULL:
+ transitionTo(mFullLockHeldState);
+ break;
+ case WIFI_MODE_FULL_HIGH_PERF:
+ transitionTo(mFullHighPerfLockHeldState);
+ break;
+ case WIFI_MODE_SCAN_ONLY:
+ transitionTo(mScanOnlyLockHeldState);
+ break;
+ default:
+ loge("Illegal lock " + mLocks.getStrongestLockMode());
+ }
+ } else {
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mScanOnlyLockHeldState);
+ } else {
+ transitionTo(mNoLockHeldState);
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+
+ pw.println("mScreenOff " + mScreenOff);
+ pw.println("mDeviceIdle " + mDeviceIdle);
+ pw.println("mPluggedType " + mPluggedType);
+ pw.println("mIdleMillis " + mIdleMillis);
+ pw.println("mSleepPolicy " + mSleepPolicy);
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
new file mode 100644
index 000000000..7aa20ec50
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2008 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.server.wifi;
+
+import android.net.NetworkInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Listens for events from the wpa_supplicant server, and passes them on
+ * to the {@link StateMachine} for handling. Runs in its own thread.
+ *
+ * @hide
+ */
+public class WifiMonitor {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "WifiMonitor";
+
+ /** Events we receive from the supplicant daemon */
+
+ private static final int CONNECTED = 1;
+ private static final int DISCONNECTED = 2;
+ private static final int STATE_CHANGE = 3;
+ private static final int SCAN_RESULTS = 4;
+ private static final int LINK_SPEED = 5;
+ private static final int TERMINATING = 6;
+ private static final int DRIVER_STATE = 7;
+ private static final int EAP_FAILURE = 8;
+ private static final int ASSOC_REJECT = 9;
+ private static final int UNKNOWN = 10;
+
+ /** All events coming from the supplicant start with this prefix */
+ private static final String EVENT_PREFIX_STR = "CTRL-EVENT-";
+ private static final int EVENT_PREFIX_LEN_STR = EVENT_PREFIX_STR.length();
+
+ /** All WPA events coming from the supplicant start with this prefix */
+ private static final String WPA_EVENT_PREFIX_STR = "WPA:";
+ private static final String PASSWORD_MAY_BE_INCORRECT_STR =
+ "pre-shared key may be incorrect";
+
+ /* WPS events */
+ private static final String WPS_SUCCESS_STR = "WPS-SUCCESS";
+
+ /* Format: WPS-FAIL msg=%d [config_error=%d] [reason=%d (%s)] */
+ private static final String WPS_FAIL_STR = "WPS-FAIL";
+ private static final String WPS_FAIL_PATTERN =
+ "WPS-FAIL msg=\\d+(?: config_error=(\\d+))?(?: reason=(\\d+))?";
+
+ /* config error code values for config_error=%d */
+ private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
+ private static final int CONFIG_AUTH_FAILURE = 18;
+
+ /* reason code values for reason=%d */
+ private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
+ private static final int REASON_WEP_PROHIBITED = 2;
+
+ private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED";
+ private static final String WPS_TIMEOUT_STR = "WPS-TIMEOUT";
+
+ /**
+ * Names of events from wpa_supplicant (minus the prefix). In the
+ * format descriptions, * &quot;<code>x</code>&quot;
+ * designates a dynamic value that needs to be parsed out from the event
+ * string
+ */
+ /**
+ * <pre>
+ * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed
+ * </pre>
+ * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point
+ */
+ private static final String CONNECTED_STR = "CONNECTED";
+ /**
+ * <pre>
+ * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys
+ * </pre>
+ */
+ private static final String DISCONNECTED_STR = "DISCONNECTED";
+ /**
+ * <pre>
+ * CTRL-EVENT-STATE-CHANGE x
+ * </pre>
+ * <code>x</code> is the numerical value of the new state.
+ */
+ private static final String STATE_CHANGE_STR = "STATE-CHANGE";
+ /**
+ * <pre>
+ * CTRL-EVENT-SCAN-RESULTS ready
+ * </pre>
+ */
+ private static final String SCAN_RESULTS_STR = "SCAN-RESULTS";
+
+ /**
+ * <pre>
+ * CTRL-EVENT-LINK-SPEED x Mb/s
+ * </pre>
+ * {@code x} is the link speed in Mb/sec.
+ */
+ private static final String LINK_SPEED_STR = "LINK-SPEED";
+ /**
+ * <pre>
+ * CTRL-EVENT-TERMINATING - signal x
+ * </pre>
+ * <code>x</code> is the signal that caused termination.
+ */
+ private static final String TERMINATING_STR = "TERMINATING";
+ /**
+ * <pre>
+ * CTRL-EVENT-DRIVER-STATE state
+ * </pre>
+ * <code>state</code> can be HANGED
+ */
+ private static final String DRIVER_STATE_STR = "DRIVER-STATE";
+ /**
+ * <pre>
+ * CTRL-EVENT-EAP-FAILURE EAP authentication failed
+ * </pre>
+ */
+ private static final String EAP_FAILURE_STR = "EAP-FAILURE";
+
+ /**
+ * This indicates an authentication failure on EAP FAILURE event
+ */
+ private static final String EAP_AUTH_FAILURE_STR = "EAP authentication failed";
+
+ /**
+ * This indicates an assoc reject event
+ */
+ private static final String ASSOC_REJECT_STR = "ASSOC-REJECT";
+
+ /**
+ * Regex pattern for extracting an Ethernet-style MAC address from a string.
+ * Matches a strings like the following:<pre>
+ * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre>
+ */
+ private static Pattern mConnectedEventPattern =
+ Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
+
+ /** P2P events */
+ private static final String P2P_EVENT_PREFIX_STR = "P2P";
+
+ /* P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 pri_dev_type=1-0050F204-1
+ name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */
+ private static final String P2P_DEVICE_FOUND_STR = "P2P-DEVICE-FOUND";
+
+ /* P2P-DEVICE-LOST p2p_dev_addr=42:fc:89:e1:e2:27 */
+ private static final String P2P_DEVICE_LOST_STR = "P2P-DEVICE-LOST";
+
+ /* P2P-FIND-STOPPED */
+ private static final String P2P_FIND_STOPPED_STR = "P2P-FIND-STOPPED";
+
+ /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */
+ private static final String P2P_GO_NEG_REQUEST_STR = "P2P-GO-NEG-REQUEST";
+
+ private static final String P2P_GO_NEG_SUCCESS_STR = "P2P-GO-NEG-SUCCESS";
+
+ /* P2P-GO-NEG-FAILURE status=x */
+ private static final String P2P_GO_NEG_FAILURE_STR = "P2P-GO-NEG-FAILURE";
+
+ private static final String P2P_GROUP_FORMATION_SUCCESS_STR =
+ "P2P-GROUP-FORMATION-SUCCESS";
+
+ private static final String P2P_GROUP_FORMATION_FAILURE_STR =
+ "P2P-GROUP-FORMATION-FAILURE";
+
+ /* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
+ [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"]
+ go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] */
+ private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED";
+
+ /* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */
+ private static final String P2P_GROUP_REMOVED_STR = "P2P-GROUP-REMOVED";
+
+ /* P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
+ bssid=fa:7b:7a:42:82:13 unknown-network */
+ private static final String P2P_INVITATION_RECEIVED_STR = "P2P-INVITATION-RECEIVED";
+
+ /* P2P-INVITATION-RESULT status=1 */
+ private static final String P2P_INVITATION_RESULT_STR = "P2P-INVITATION-RESULT";
+
+ /* P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_PBC_REQ_STR = "P2P-PROV-DISC-PBC-REQ";
+
+ /* P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36 */
+ private static final String P2P_PROV_DISC_PBC_RSP_STR = "P2P-PROV-DISC-PBC-RESP";
+
+ /* P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_ENTER_PIN_STR = "P2P-PROV-DISC-ENTER-PIN";
+ /* P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
+ /* P2P-PROV-DISC-FAILURE p2p_dev_addr=42:fc:89:e1:e2:27 */
+ private static final String P2P_PROV_DISC_FAILURE_STR = "P2P-PROV-DISC-FAILURE";
+
+ /*
+ * Protocol format is as follows.<br>
+ * See the Table.62 in the WiFi Direct specification for the detail.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
+ * length=3, service type=0(ALL Service), transaction id=1,
+ * status=1(service protocol type not available)<br>
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
+ * length=3, service type=2(UPnP), transaction id=2,
+ * status=1(service protocol type not available)
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
+ * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
+ * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
+ * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
+ * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
+ * 576696365
+ * length=153,type=2(UPnP),transaction id=3,status=0
+ *
+ * UPnP Protocol format is as follows.
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ *
+ * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
+ * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
+ * ede-8574-59ab-9332-123456789012::upnp:rootdevice
+ *
+ * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
+ * 70c00c000c01094d795072696e746572c027
+ * length=25, type=1(Bonjour),transaction id=2,status=0
+ *
+ * Bonjour Protocol format is as follows.
+ * __________________________________________________________
+ * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
+ *
+ * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
+ * RDATA=MyPrinter._ipp._tcp.local.
+ *
+ */
+ private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
+
+ private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
+ /* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
+ private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
+ /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */
+ private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED";
+
+ /* Supplicant events reported to a state machine */
+ private static final int BASE = Protocol.BASE_WIFI_MONITOR;
+
+ /* Connection to supplicant established */
+ public static final int SUP_CONNECTION_EVENT = BASE + 1;
+ /* Connection to supplicant lost */
+ public static final int SUP_DISCONNECTION_EVENT = BASE + 2;
+ /* Network connection completed */
+ public static final int NETWORK_CONNECTION_EVENT = BASE + 3;
+ /* Network disconnection completed */
+ public static final int NETWORK_DISCONNECTION_EVENT = BASE + 4;
+ /* Scan results are available */
+ public static final int SCAN_RESULTS_EVENT = BASE + 5;
+ /* Supplicate state changed */
+ public static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 6;
+ /* Password failure and EAP authentication failure */
+ public static final int AUTHENTICATION_FAILURE_EVENT = BASE + 7;
+ /* WPS success detected */
+ public static final int WPS_SUCCESS_EVENT = BASE + 8;
+ /* WPS failure detected */
+ public static final int WPS_FAIL_EVENT = BASE + 9;
+ /* WPS overlap detected */
+ public static final int WPS_OVERLAP_EVENT = BASE + 10;
+ /* WPS timeout detected */
+ public static final int WPS_TIMEOUT_EVENT = BASE + 11;
+ /* Driver was hung */
+ public static final int DRIVER_HUNG_EVENT = BASE + 12;
+
+ /* P2P events */
+ public static final int P2P_DEVICE_FOUND_EVENT = BASE + 21;
+ public static final int P2P_DEVICE_LOST_EVENT = BASE + 22;
+ public static final int P2P_GO_NEGOTIATION_REQUEST_EVENT = BASE + 23;
+ public static final int P2P_GO_NEGOTIATION_SUCCESS_EVENT = BASE + 25;
+ public static final int P2P_GO_NEGOTIATION_FAILURE_EVENT = BASE + 26;
+ public static final int P2P_GROUP_FORMATION_SUCCESS_EVENT = BASE + 27;
+ public static final int P2P_GROUP_FORMATION_FAILURE_EVENT = BASE + 28;
+ public static final int P2P_GROUP_STARTED_EVENT = BASE + 29;
+ public static final int P2P_GROUP_REMOVED_EVENT = BASE + 30;
+ public static final int P2P_INVITATION_RECEIVED_EVENT = BASE + 31;
+ public static final int P2P_INVITATION_RESULT_EVENT = BASE + 32;
+ public static final int P2P_PROV_DISC_PBC_REQ_EVENT = BASE + 33;
+ public static final int P2P_PROV_DISC_PBC_RSP_EVENT = BASE + 34;
+ public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 35;
+ public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 36;
+ public static final int P2P_FIND_STOPPED_EVENT = BASE + 37;
+ public static final int P2P_SERV_DISC_RESP_EVENT = BASE + 38;
+ public static final int P2P_PROV_DISC_FAILURE_EVENT = BASE + 39;
+
+ /* hostap events */
+ public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41;
+ public static final int AP_STA_CONNECTED_EVENT = BASE + 42;
+
+ /* Indicates assoc reject event */
+ public static final int ASSOCIATION_REJECTION_EVENT = BASE + 43;
+
+ /**
+ * This indicates a read error on the monitor socket conenction
+ */
+ private static final String WPA_RECV_ERROR_STR = "recv error";
+
+ /**
+ * Max errors before we close supplicant connection
+ */
+ private static final int MAX_RECV_ERRORS = 10;
+
+ private final String mInterfaceName;
+ private final WifiNative mWifiNative;
+ private final StateMachine mStateMachine;
+ private boolean mMonitoring;
+
+ // This is a global counter, since it's not monitor specific. However, the existing
+ // implementation forwards all "global" control events like CTRL-EVENT-TERMINATING
+ // to the p2p0 monitor. Is that expected ? It seems a bit surprising.
+ //
+ // TODO: If the p2p0 monitor isn't registered, the behaviour is even more surprising.
+ // The event will be dispatched to all monitors, and each of them will end up incrementing
+ // it in their dispatchXXX method. If we have 5 registered monitors (say), 2 consecutive
+ // recv errors will cause us to disconnect from the supplicant (instead of the intended 10).
+ //
+ // This variable is always accessed and modified under a WifiMonitorSingleton lock.
+ private static int sRecvErrors;
+
+ public WifiMonitor(StateMachine wifiStateMachine, WifiNative wifiNative) {
+ if (DBG) Log.d(TAG, "Creating WifiMonitor");
+ mWifiNative = wifiNative;
+ mInterfaceName = wifiNative.mInterfaceName;
+ mStateMachine = wifiStateMachine;
+ mMonitoring = false;
+
+ WifiMonitorSingleton.sInstance.registerInterfaceMonitor(mInterfaceName, this);
+ }
+
+ public void startMonitoring() {
+ WifiMonitorSingleton.sInstance.startMonitoring(mInterfaceName);
+ }
+
+ public void stopMonitoring() {
+ WifiMonitorSingleton.sInstance.stopMonitoring(mInterfaceName);
+ }
+
+ public void stopSupplicant() {
+ WifiMonitorSingleton.sInstance.stopSupplicant();
+ }
+
+ public void killSupplicant(boolean p2pSupported) {
+ WifiMonitorSingleton.sInstance.killSupplicant(p2pSupported);
+ }
+
+ private static class WifiMonitorSingleton {
+ private static final WifiMonitorSingleton sInstance = new WifiMonitorSingleton();
+
+ private final HashMap<String, WifiMonitor> mIfaceMap = new HashMap<String, WifiMonitor>();
+ private boolean mConnected = false;
+ private WifiNative mWifiNative;
+
+ private WifiMonitorSingleton() {
+ }
+
+ public synchronized void startMonitoring(String iface) {
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (m == null) {
+ Log.e(TAG, "startMonitor called with unknown iface=" + iface);
+ return;
+ }
+
+ Log.d(TAG, "startMonitoring(" + iface + ") with mConnected = " + mConnected);
+
+ if (mConnected) {
+ m.mMonitoring = true;
+ m.mStateMachine.sendMessage(SUP_CONNECTION_EVENT);
+ } else {
+ if (DBG) Log.d(TAG, "connecting to supplicant");
+ int connectTries = 0;
+ while (true) {
+ if (mWifiNative.connectToSupplicant()) {
+ m.mMonitoring = true;
+ m.mStateMachine.sendMessage(SUP_CONNECTION_EVENT);
+ new MonitorThread(mWifiNative, this).start();
+ mConnected = true;
+ break;
+ }
+ if (connectTries++ < 5) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignore) {
+ }
+ } else {
+ mIfaceMap.remove(iface);
+ m.mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ Log.e(TAG, "startMonitoring(" + iface + ") failed!");
+ break;
+ }
+ }
+ }
+ }
+
+ public synchronized void stopMonitoring(String iface) {
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (DBG) Log.d(TAG, "stopMonitoring(" + iface + ") = " + m.mStateMachine);
+ m.mMonitoring = false;
+ m.mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ }
+
+ public synchronized void registerInterfaceMonitor(String iface, WifiMonitor m) {
+ if (DBG) Log.d(TAG, "registerInterface(" + iface + "+" + m.mStateMachine + ")");
+ mIfaceMap.put(iface, m);
+ if (mWifiNative == null) {
+ mWifiNative = m.mWifiNative;
+ }
+ }
+
+ public synchronized void unregisterInterfaceMonitor(String iface) {
+ // REVIEW: When should we call this? If this isn't called, then WifiMonitor
+ // objects will remain in the mIfaceMap; and won't ever get deleted
+
+ WifiMonitor m = mIfaceMap.remove(iface);
+ if (DBG) Log.d(TAG, "unregisterInterface(" + iface + "+" + m.mStateMachine + ")");
+ }
+
+ public synchronized void stopSupplicant() {
+ mWifiNative.stopSupplicant();
+ }
+
+ public synchronized void killSupplicant(boolean p2pSupported) {
+ WifiNative.killSupplicant(p2pSupported);
+ mConnected = false;
+ for (WifiMonitor m : mIfaceMap.values()) {
+ m.mMonitoring = false;
+ }
+ }
+
+ private synchronized boolean dispatchEvent(String eventStr) {
+ String iface;
+ if (eventStr.startsWith("IFNAME=")) {
+ int space = eventStr.indexOf(' ');
+ if (space != -1) {
+ iface = eventStr.substring(7, space);
+ if (!mIfaceMap.containsKey(iface) && iface.startsWith("p2p-")) {
+ // p2p interfaces are created dynamically, but we have
+ // only one P2p state machine monitoring all of them; look
+ // for it explicitly, and send messages there ..
+ iface = "p2p0";
+ }
+ eventStr = eventStr.substring(space + 1);
+ } else {
+ // No point dispatching this event to any interface, the dispatched
+ // event string will begin with "IFNAME=" which dispatchEvent can't really
+ // do anything about.
+ Log.e(TAG, "Dropping malformed event (unparsable iface): " + eventStr);
+ return false;
+ }
+ } else {
+ // events without prefix belong to p2p0 monitor
+ iface = "p2p0";
+ }
+
+ if (DBG) Log.d(TAG, "Dispatching event to interface: " + iface);
+
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (m != null) {
+ if (m.mMonitoring) {
+ if (m.dispatchEvent(eventStr)) {
+ mConnected = false;
+ return true;
+ }
+
+ return false;
+ } else {
+ if (DBG) Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+ return false;
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Sending to all monitors because there's no matching iface");
+ boolean done = false;
+ for (WifiMonitor monitor : mIfaceMap.values()) {
+ if (monitor.mMonitoring && monitor.dispatchEvent(eventStr)) {
+ done = true;
+ }
+ }
+
+ if (done) {
+ mConnected = false;
+ }
+
+ return done;
+ }
+ }
+ }
+
+ private static class MonitorThread extends Thread {
+ private final WifiNative mWifiNative;
+ private final WifiMonitorSingleton mWifiMonitorSingleton;
+
+ public MonitorThread(WifiNative wifiNative, WifiMonitorSingleton wifiMonitorSingleton) {
+ super("WifiMonitor");
+ mWifiNative = wifiNative;
+ mWifiMonitorSingleton = wifiMonitorSingleton;
+ }
+
+ public void run() {
+ //noinspection InfiniteLoopStatement
+ for (;;) {
+ String eventStr = mWifiNative.waitForEvent();
+
+ // Skip logging the common but mostly uninteresting scan-results event
+ if (DBG && eventStr.indexOf(SCAN_RESULTS_STR) == -1) {
+ Log.d(TAG, "Event [" + eventStr + "]");
+ }
+
+ if (mWifiMonitorSingleton.dispatchEvent(eventStr)) {
+ if (DBG) Log.d(TAG, "Disconnecting from the supplicant, no more events");
+ break;
+ }
+ }
+ }
+ }
+
+ /* @return true if the event was supplicant disconnection */
+ private boolean dispatchEvent(String eventStr) {
+
+ if (!eventStr.startsWith(EVENT_PREFIX_STR)) {
+ if (eventStr.startsWith(WPA_EVENT_PREFIX_STR) &&
+ 0 < eventStr.indexOf(PASSWORD_MAY_BE_INCORRECT_STR)) {
+ mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT);
+ } else if (eventStr.startsWith(WPS_SUCCESS_STR)) {
+ mStateMachine.sendMessage(WPS_SUCCESS_EVENT);
+ } else if (eventStr.startsWith(WPS_FAIL_STR)) {
+ handleWpsFailEvent(eventStr);
+ } else if (eventStr.startsWith(WPS_OVERLAP_STR)) {
+ mStateMachine.sendMessage(WPS_OVERLAP_EVENT);
+ } else if (eventStr.startsWith(WPS_TIMEOUT_STR)) {
+ mStateMachine.sendMessage(WPS_TIMEOUT_EVENT);
+ } else if (eventStr.startsWith(P2P_EVENT_PREFIX_STR)) {
+ handleP2pEvents(eventStr);
+ } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) {
+ handleHostApEvents(eventStr);
+ }
+ else {
+ if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
+ }
+ return false;
+ }
+
+ String eventName = eventStr.substring(EVENT_PREFIX_LEN_STR);
+ int nameEnd = eventName.indexOf(' ');
+ if (nameEnd != -1)
+ eventName = eventName.substring(0, nameEnd);
+ if (eventName.length() == 0) {
+ if (DBG) Log.i(TAG, "Received wpa_supplicant event with empty event name");
+ return false;
+ }
+ /*
+ * Map event name into event enum
+ */
+ int event;
+ if (eventName.equals(CONNECTED_STR))
+ event = CONNECTED;
+ else if (eventName.equals(DISCONNECTED_STR))
+ event = DISCONNECTED;
+ else if (eventName.equals(STATE_CHANGE_STR))
+ event = STATE_CHANGE;
+ else if (eventName.equals(SCAN_RESULTS_STR))
+ event = SCAN_RESULTS;
+ else if (eventName.equals(LINK_SPEED_STR))
+ event = LINK_SPEED;
+ else if (eventName.equals(TERMINATING_STR))
+ event = TERMINATING;
+ else if (eventName.equals(DRIVER_STATE_STR))
+ event = DRIVER_STATE;
+ else if (eventName.equals(EAP_FAILURE_STR))
+ event = EAP_FAILURE;
+ else if (eventName.equals(ASSOC_REJECT_STR))
+ event = ASSOC_REJECT;
+ else
+ event = UNKNOWN;
+
+ String eventData = eventStr;
+ if (event == DRIVER_STATE || event == LINK_SPEED)
+ eventData = eventData.split(" ")[1];
+ else if (event == STATE_CHANGE || event == EAP_FAILURE) {
+ int ind = eventStr.indexOf(" ");
+ if (ind != -1) {
+ eventData = eventStr.substring(ind + 1);
+ }
+ } else {
+ int ind = eventStr.indexOf(" - ");
+ if (ind != -1) {
+ eventData = eventStr.substring(ind + 3);
+ }
+ }
+
+ if (event == STATE_CHANGE) {
+ handleSupplicantStateChange(eventData);
+ } else if (event == DRIVER_STATE) {
+ handleDriverEvent(eventData);
+ } else if (event == TERMINATING) {
+ /**
+ * Close the supplicant connection if we see
+ * too many recv errors
+ */
+ if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
+ if (++sRecvErrors > MAX_RECV_ERRORS) {
+ if (DBG) {
+ Log.d(TAG, "too many recv errors, closing connection");
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // notify and exit
+ mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ return true;
+ } else if (event == EAP_FAILURE) {
+ if (eventData.startsWith(EAP_AUTH_FAILURE_STR)) {
+ mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT);
+ }
+ } else if (event == ASSOC_REJECT) {
+ mStateMachine.sendMessage(ASSOCIATION_REJECTION_EVENT);
+ } else {
+ handleEvent(event, eventData);
+ }
+ sRecvErrors = 0;
+ return false;
+ }
+
+ private void handleDriverEvent(String state) {
+ if (state == null) {
+ return;
+ }
+ if (state.equals("HANGED")) {
+ mStateMachine.sendMessage(DRIVER_HUNG_EVENT);
+ }
+ }
+
+ /**
+ * Handle all supplicant events except STATE-CHANGE
+ * @param event the event type
+ * @param remainder the rest of the string following the
+ * event name and &quot;&#8195;&#8212;&#8195;&quot;
+ */
+ void handleEvent(int event, String remainder) {
+ switch (event) {
+ case DISCONNECTED:
+ handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
+ break;
+
+ case CONNECTED:
+ handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
+ break;
+
+ case SCAN_RESULTS:
+ mStateMachine.sendMessage(SCAN_RESULTS_EVENT);
+ break;
+
+ case UNKNOWN:
+ break;
+ }
+ }
+
+ private void handleWpsFailEvent(String dataString) {
+ final Pattern p = Pattern.compile(WPS_FAIL_PATTERN);
+ Matcher match = p.matcher(dataString);
+ if (match.find()) {
+ String cfgErr = match.group(1);
+ String reason = match.group(2);
+
+ if (reason != null) {
+ switch(Integer.parseInt(reason)) {
+ case REASON_TKIP_ONLY_PROHIBITED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_TKIP_ONLY_PROHIBITED, 0));
+ return;
+ case REASON_WEP_PROHIBITED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_WEP_PROHIBITED, 0));
+ return;
+ }
+ }
+ if (cfgErr != null) {
+ switch(Integer.parseInt(cfgErr)) {
+ case CONFIG_AUTH_FAILURE:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_AUTH_FAILURE, 0));
+ return;
+ case CONFIG_MULTIPLE_PBC_DETECTED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_OVERLAP_ERROR, 0));
+ return;
+ }
+ }
+ }
+ //For all other errors, return a generic internal error
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.ERROR, 0));
+ }
+
+ /* <event> status=<err> and the special case of <event> reason=FREQ_CONFLICT */
+ private P2pStatus p2pError(String dataString) {
+ P2pStatus err = P2pStatus.UNKNOWN;
+ String[] tokens = dataString.split(" ");
+ if (tokens.length < 2) return err;
+ String[] nameValue = tokens[1].split("=");
+ if (nameValue.length != 2) return err;
+
+ /* Handle the special case of reason=FREQ+CONFLICT */
+ if (nameValue[1].equals("FREQ_CONFLICT")) {
+ return P2pStatus.NO_COMMON_CHANNEL;
+ }
+ try {
+ err = P2pStatus.valueOf(Integer.parseInt(nameValue[1]));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ return err;
+ }
+
+ /**
+ * Handle p2p events
+ */
+ private void handleP2pEvents(String dataString) {
+ if (dataString.startsWith(P2P_DEVICE_FOUND_STR)) {
+ mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, new WifiP2pDevice(dataString));
+ } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) {
+ mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, new WifiP2pDevice(dataString));
+ } else if (dataString.startsWith(P2P_FIND_STOPPED_STR)) {
+ mStateMachine.sendMessage(P2P_FIND_STOPPED_EVENT);
+ } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_REQUEST_EVENT,
+ new WifiP2pConfig(dataString));
+ } else if (dataString.startsWith(P2P_GO_NEG_SUCCESS_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_SUCCESS_EVENT);
+ } else if (dataString.startsWith(P2P_GO_NEG_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_FAILURE_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_FORMATION_SUCCESS_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_FORMATION_SUCCESS_EVENT);
+ } else if (dataString.startsWith(P2P_GROUP_FORMATION_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_FORMATION_FAILURE_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_STARTED_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_STARTED_EVENT, new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_REMOVED_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_REMOVED_EVENT, new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_INVITATION_RECEIVED_STR)) {
+ mStateMachine.sendMessage(P2P_INVITATION_RECEIVED_EVENT,
+ new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_INVITATION_RESULT_STR)) {
+ mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_PBC_RSP_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_PBC_RSP_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_ENTER_PIN_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_ENTER_PIN_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_FAILURE_EVENT);
+ } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
+ List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
+ if (list != null) {
+ mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list);
+ } else {
+ Log.e(TAG, "Null service resp " + dataString);
+ }
+ }
+ }
+
+ /**
+ * Handle hostap events
+ */
+ private void handleHostApEvents(String dataString) {
+ String[] tokens = dataString.split(" ");
+ /* AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
+ if (tokens[0].equals(AP_STA_CONNECTED_STR)) {
+ mStateMachine.sendMessage(AP_STA_CONNECTED_EVENT, new WifiP2pDevice(dataString));
+ /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
+ } else if (tokens[0].equals(AP_STA_DISCONNECTED_STR)) {
+ mStateMachine.sendMessage(AP_STA_DISCONNECTED_EVENT, new WifiP2pDevice(dataString));
+ }
+ }
+
+ /**
+ * Handle the supplicant STATE-CHANGE event
+ * @param dataString New supplicant state string in the format:
+ * id=network-id state=new-state
+ */
+ private void handleSupplicantStateChange(String dataString) {
+ WifiSsid wifiSsid = null;
+ int index = dataString.lastIndexOf("SSID=");
+ if (index != -1) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ dataString.substring(index + 5));
+ }
+ String[] dataTokens = dataString.split(" ");
+
+ String BSSID = null;
+ int networkId = -1;
+ int newState = -1;
+ for (String token : dataTokens) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) {
+ continue;
+ }
+
+ if (nameValue[0].equals("BSSID")) {
+ BSSID = nameValue[1];
+ continue;
+ }
+
+ int value;
+ try {
+ value = Integer.parseInt(nameValue[1]);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ if (nameValue[0].equals("id")) {
+ networkId = value;
+ } else if (nameValue[0].equals("state")) {
+ newState = value;
+ }
+ }
+
+ if (newState == -1) return;
+
+ SupplicantState newSupplicantState = SupplicantState.INVALID;
+ for (SupplicantState state : SupplicantState.values()) {
+ if (state.ordinal() == newState) {
+ newSupplicantState = state;
+ break;
+ }
+ }
+ if (newSupplicantState == SupplicantState.INVALID) {
+ Log.w(TAG, "Invalid supplicant state: " + newState);
+ }
+ notifySupplicantStateChange(networkId, wifiSsid, BSSID, newSupplicantState);
+ }
+
+ private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
+ String BSSID = null;
+ int networkId = -1;
+ if (newState == NetworkInfo.DetailedState.CONNECTED) {
+ Matcher match = mConnectedEventPattern.matcher(data);
+ if (!match.find()) {
+ if (DBG) Log.d(TAG, "Could not find BSSID in CONNECTED event string");
+ } else {
+ BSSID = match.group(1);
+ try {
+ networkId = Integer.parseInt(match.group(2));
+ } catch (NumberFormatException e) {
+ networkId = -1;
+ }
+ }
+ notifyNetworkStateChange(newState, BSSID, networkId);
+ }
+ }
+
+ /**
+ * Send the state machine a notification that the state of Wifi connectivity
+ * has changed.
+ * @param newState the new network state
+ * @param BSSID when the new state is {@link NetworkInfo.DetailedState#CONNECTED},
+ * this is the MAC address of the access point. Otherwise, it
+ * is {@code null}.
+ * @param netId the configured network on which the state change occurred
+ */
+ void notifyNetworkStateChange(NetworkInfo.DetailedState newState, String BSSID, int netId) {
+ if (newState == NetworkInfo.DetailedState.CONNECTED) {
+ Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT,
+ netId, 0, BSSID);
+ mStateMachine.sendMessage(m);
+ } else {
+ Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT,
+ netId, 0, BSSID);
+ mStateMachine.sendMessage(m);
+ }
+ }
+
+ /**
+ * Send the state machine a notification that the state of the supplicant
+ * has changed.
+ * @param networkId the configured network on which the state change occurred
+ * @param wifiSsid network name
+ * @param BSSID network address
+ * @param newState the new {@code SupplicantState}
+ */
+ void notifySupplicantStateChange(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState newState) {
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
+ new StateChangeResult(networkId, wifiSsid, BSSID, newState)));
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
new file mode 100644
index 000000000..6dbdfea96
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -0,0 +1,982 @@
+/*
+ * Copyright (C) 2008 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.server.wifi;
+
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.text.TextUtils;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Native calls for bring up/shut down of the supplicant daemon and for
+ * sending requests to the supplicant daemon
+ *
+ * waitForEvent() is called on the monitor thread for events. All other methods
+ * must be serialized from the framework.
+ *
+ * {@hide}
+ */
+public class WifiNative {
+
+ private static final boolean DBG = false;
+ private final String mTAG;
+ private static final int DEFAULT_GROUP_OWNER_INTENT = 6;
+
+ static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0;
+ static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1;
+ static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2;
+
+ static final int SCAN_WITHOUT_CONNECTION_SETUP = 1;
+ static final int SCAN_WITH_CONNECTION_SETUP = 2;
+
+ // Hold this lock before calling supplicant - it is required to
+ // mutually exclude access from Wifi and P2p state machines
+ static final Object mLock = new Object();
+
+ public final String mInterfaceName;
+ public final String mInterfacePrefix;
+
+ private boolean mSuspendOptEnabled = false;
+
+ /* Register native functions */
+
+ static {
+ /* Native functions are defined in libwifi-service.so */
+ System.loadLibrary("wifi-service");
+ registerNatives();
+ }
+
+ private static native int registerNatives();
+
+ public native static boolean loadDriver();
+
+ public native static boolean isDriverLoaded();
+
+ public native static boolean unloadDriver();
+
+ public native static boolean startSupplicant(boolean p2pSupported);
+
+ /* Sends a kill signal to supplicant. To be used when we have lost connection
+ or when the supplicant is hung */
+ public native static boolean killSupplicant(boolean p2pSupported);
+
+ private native boolean connectToSupplicantNative();
+
+ private native void closeSupplicantConnectionNative();
+
+ /**
+ * Wait for the supplicant to send an event, returning the event string.
+ * @return the event string sent by the supplicant.
+ */
+ private native String waitForEventNative();
+
+ private native boolean doBooleanCommandNative(String command);
+
+ private native int doIntCommandNative(String command);
+
+ private native String doStringCommandNative(String command);
+
+ public WifiNative(String interfaceName) {
+ mInterfaceName = interfaceName;
+ mTAG = "WifiNative-" + interfaceName;
+ if (!interfaceName.equals("p2p0")) {
+ mInterfacePrefix = "IFNAME=" + interfaceName + " ";
+ } else {
+ // commands for p2p0 interface don't need prefix
+ mInterfacePrefix = "";
+ }
+ }
+
+ private static final LocalLog mLocalLog = new LocalLog(1024);
+
+ // hold mLock before accessing mCmdIdLock
+ private int mCmdId;
+
+ public LocalLog getLocalLog() {
+ return mLocalLog;
+ }
+
+ private int getNewCmdIdLocked() {
+ return mCmdId++;
+ }
+
+ private void localLog(String s) {
+ if (mLocalLog != null)
+ mLocalLog.log(mInterfaceName + ": " + s);
+ }
+
+ public boolean connectToSupplicant() {
+ // No synchronization necessary .. it is implemented in WifiMonitor
+ localLog(mInterfacePrefix + "connectToSupplicant");
+ return connectToSupplicantNative();
+ }
+
+ public void closeSupplicantConnection() {
+ localLog(mInterfacePrefix + "closeSupplicantConnection");
+ closeSupplicantConnectionNative();
+ }
+
+ public String waitForEvent() {
+ // No synchronization necessary .. it is implemented in WifiMonitor
+ return waitForEventNative();
+ }
+
+ private boolean doBooleanCommand(String command) {
+ if (DBG) Log.d(mTAG, "doBoolean: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ boolean result = doBooleanCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private int doIntCommand(String command) {
+ if (DBG) Log.d(mTAG, "doInt: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ int result = doIntCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private String doStringCommand(String command) {
+ if (DBG) Log.d(mTAG, "doString: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ String result = doStringCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private String doStringCommandWithoutLogging(String command) {
+ if (DBG) Log.d(mTAG, "doString: " + command);
+ synchronized (mLock) {
+ return doStringCommandNative(mInterfacePrefix + command);
+ }
+ }
+
+ public boolean ping() {
+ String pong = doStringCommand("PING");
+ return (pong != null && pong.equals("PONG"));
+ }
+
+ public boolean scan(int type) {
+ if (type == SCAN_WITHOUT_CONNECTION_SETUP) {
+ return doBooleanCommand("SCAN TYPE=ONLY");
+ } else if (type == SCAN_WITH_CONNECTION_SETUP) {
+ return doBooleanCommand("SCAN");
+ } else {
+ throw new IllegalArgumentException("Invalid scan type");
+ }
+ }
+
+ /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta.
+ *
+ * Note that underneath we use a harsh-sounding "terminate" supplicant command
+ * for a graceful stop and a mild-sounding "stop" interface
+ * to kill the process
+ */
+ public boolean stopSupplicant() {
+ return doBooleanCommand("TERMINATE");
+ }
+
+ public String listNetworks() {
+ return doStringCommand("LIST_NETWORKS");
+ }
+
+ public int addNetwork() {
+ return doIntCommand("ADD_NETWORK");
+ }
+
+ public boolean setNetworkVariable(int netId, String name, String value) {
+ if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
+ return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
+ }
+
+ public String getNetworkVariable(int netId, String name) {
+ if (TextUtils.isEmpty(name)) return null;
+
+ // GET_NETWORK will likely flood the logs ...
+ return doStringCommandWithoutLogging("GET_NETWORK " + netId + " " + name);
+ }
+
+ public boolean removeNetwork(int netId) {
+ return doBooleanCommand("REMOVE_NETWORK " + netId);
+ }
+
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ if (disableOthers) {
+ return doBooleanCommand("SELECT_NETWORK " + netId);
+ } else {
+ return doBooleanCommand("ENABLE_NETWORK " + netId);
+ }
+ }
+
+ public boolean disableNetwork(int netId) {
+ return doBooleanCommand("DISABLE_NETWORK " + netId);
+ }
+
+ public boolean reconnect() {
+ return doBooleanCommand("RECONNECT");
+ }
+
+ public boolean reassociate() {
+ return doBooleanCommand("REASSOCIATE");
+ }
+
+ public boolean disconnect() {
+ return doBooleanCommand("DISCONNECT");
+ }
+
+ public String status() {
+ return doStringCommand("STATUS");
+ }
+
+ public String getMacAddress() {
+ //Macaddr = XX.XX.XX.XX.XX.XX
+ String ret = doStringCommand("DRIVER MACADDR");
+ if (!TextUtils.isEmpty(ret)) {
+ String[] tokens = ret.split(" = ");
+ if (tokens.length == 2) return tokens[1];
+ }
+ return null;
+ }
+
+ /**
+ * Format of results:
+ * =================
+ * id=1
+ * bssid=68:7f:74:d7:1b:6e
+ * freq=2412
+ * level=-43
+ * tsf=1344621975160944
+ * age=2623
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zubyb
+ * ====
+ *
+ * RANGE=ALL gets all scan results
+ * RANGE=ID- gets results from ID
+ * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
+ */
+ public String scanResults(int sid) {
+ return doStringCommandWithoutLogging("BSS RANGE=" + sid + "- MASK=0x21987");
+ }
+
+ /**
+ * Format of command
+ * DRIVER WLS_BATCHING SET SCANFREQ=x MSCAN=r BESTN=y CHANNEL=<z, w, t> RTT=s
+ * where x is an ascii representation of an integer number of seconds between scans
+ * r is an ascii representation of an integer number of scans per batch
+ * y is an ascii representation of an integer number of the max AP to remember per scan
+ * z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
+ * indicating entire ranges of channels
+ * s is an ascii representation of an integer number of highest-strength AP
+ * for which we'd like approximate distance reported
+ *
+ * The return value is an ascii integer representing a guess of the number of scans
+ * the firmware can remember before it runs out of buffer space or -1 on error
+ */
+ public String setBatchedScanSettings(BatchedScanSettings settings) {
+ if (settings == null) {
+ return doStringCommand("DRIVER WLS_BATCHING STOP");
+ }
+ String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec;
+ cmd += " MSCAN=" + settings.maxScansPerBatch;
+ if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
+ cmd += " BESTN=" + settings.maxApPerScan;
+ }
+ if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
+ cmd += " CHANNEL=<";
+ int i = 0;
+ for (String channel : settings.channelSet) {
+ cmd += (i > 0 ? "," : "") + channel;
+ ++i;
+ }
+ cmd += ">";
+ }
+ if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
+ cmd += " RTT=" + settings.maxApForDistance;
+ }
+ return doStringCommand(cmd);
+ }
+
+ public String getBatchedScanResults() {
+ return doStringCommand("DRIVER WLS_BATCHING GET");
+ }
+
+ public boolean startDriver() {
+ return doBooleanCommand("DRIVER START");
+ }
+
+ public boolean stopDriver() {
+ return doBooleanCommand("DRIVER STOP");
+ }
+
+
+ /**
+ * Start filtering out Multicast V4 packets
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ *
+ * Multicast filtering rules work as follows:
+ *
+ * The driver can filter multicast (v4 and/or v6) and broadcast packets when in
+ * a power optimized mode (typically when screen goes off).
+ *
+ * In order to prevent the driver from filtering the multicast/broadcast packets, we have to
+ * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective
+ *
+ * DRIVER RXFILTER-ADD Num
+ * where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6
+ *
+ * and DRIVER RXFILTER-START
+ * In order to stop the usage of these rules, we do
+ *
+ * DRIVER RXFILTER-STOP
+ * DRIVER RXFILTER-REMOVE Num
+ * where Num is as described for RXFILTER-ADD
+ *
+ * The SETSUSPENDOPT driver command overrides the filtering rules
+ */
+ public boolean startFilteringMulticastV4Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-REMOVE 2")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Stop filtering out Multicast V4 packets.
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean stopFilteringMulticastV4Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-ADD 2")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Start filtering out Multicast V6 packets
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean startFilteringMulticastV6Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-REMOVE 3")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Stop filtering out Multicast V6 packets.
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean stopFilteringMulticastV6Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-ADD 3")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ public int getBand() {
+ String ret = doStringCommand("DRIVER GETBAND");
+ if (!TextUtils.isEmpty(ret)) {
+ //reply is "BAND X" where X is the band
+ String[] tokens = ret.split(" ");
+ try {
+ if (tokens.length == 2) return Integer.parseInt(tokens[1]);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ public boolean setBand(int band) {
+ return doBooleanCommand("DRIVER SETBAND " + band);
+ }
+
+ /**
+ * Sets the bluetooth coexistence mode.
+ *
+ * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED},
+ * {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or
+ * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}.
+ * @return Whether the mode was successfully set.
+ */
+ public boolean setBluetoothCoexistenceMode(int mode) {
+ return doBooleanCommand("DRIVER BTCOEXMODE " + mode);
+ }
+
+ /**
+ * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
+ * some of the low-level scan parameters used by the driver are changed to
+ * reduce interference with A2DP streaming.
+ *
+ * @param isSet whether to enable or disable this mode
+ * @return {@code true} if the command succeeded, {@code false} otherwise.
+ */
+ public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) {
+ if (setCoexScanMode) {
+ return doBooleanCommand("DRIVER BTCOEXSCAN-START");
+ } else {
+ return doBooleanCommand("DRIVER BTCOEXSCAN-STOP");
+ }
+ }
+
+ public boolean saveConfig() {
+ return doBooleanCommand("SAVE_CONFIG");
+ }
+
+ public boolean addToBlacklist(String bssid) {
+ if (TextUtils.isEmpty(bssid)) return false;
+ return doBooleanCommand("BLACKLIST " + bssid);
+ }
+
+ public boolean clearBlacklist() {
+ return doBooleanCommand("BLACKLIST clear");
+ }
+
+ public boolean setSuspendOptimizations(boolean enabled) {
+ if (mSuspendOptEnabled == enabled) return true;
+ mSuspendOptEnabled = enabled;
+ if (enabled) {
+ return doBooleanCommand("DRIVER SETSUSPENDMODE 1");
+ } else {
+ return doBooleanCommand("DRIVER SETSUSPENDMODE 0");
+ }
+ }
+
+ public boolean setCountryCode(String countryCode) {
+ return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
+ }
+
+ public void enableBackgroundScan(boolean enable) {
+ if (enable) {
+ doBooleanCommand("SET pno 1");
+ } else {
+ doBooleanCommand("SET pno 0");
+ }
+ }
+
+ public void setScanInterval(int scanInterval) {
+ doBooleanCommand("SCAN_INTERVAL " + scanInterval);
+ }
+
+ public void startTdls(String macAddr, boolean enable) {
+ if (enable) {
+ doBooleanCommand("TDLS_DISCOVER " + macAddr);
+ doBooleanCommand("TDLS_SETUP " + macAddr);
+ } else {
+ doBooleanCommand("TDLS_TEARDOWN " + macAddr);
+ }
+ }
+
+ /** Example output:
+ * RSSI=-65
+ * LINKSPEED=48
+ * NOISE=9999
+ * FREQUENCY=0
+ */
+ public String signalPoll() {
+ return doStringCommandWithoutLogging("SIGNAL_POLL");
+ }
+
+ /** Example outout:
+ * TXGOOD=396
+ * TXBAD=1
+ */
+ public String pktcntPoll() {
+ return doStringCommand("PKTCNT_POLL");
+ }
+
+ public void bssFlush() {
+ doBooleanCommand("BSS_FLUSH 0");
+ }
+
+ public boolean startWpsPbc(String bssid) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doBooleanCommand("WPS_PBC");
+ } else {
+ return doBooleanCommand("WPS_PBC " + bssid);
+ }
+ }
+
+ public boolean startWpsPbc(String iface, String bssid) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC");
+ } else {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC " + bssid);
+ }
+ }
+ }
+
+ public boolean startWpsPinKeypad(String pin) {
+ if (TextUtils.isEmpty(pin)) return false;
+ return doBooleanCommand("WPS_PIN any " + pin);
+ }
+
+ public boolean startWpsPinKeypad(String iface, String pin) {
+ if (TextUtils.isEmpty(pin)) return false;
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PIN any " + pin);
+ }
+ }
+
+
+ public String startWpsPinDisplay(String bssid) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doStringCommand("WPS_PIN any");
+ } else {
+ return doStringCommand("WPS_PIN " + bssid);
+ }
+ }
+
+ public String startWpsPinDisplay(String iface, String bssid) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doStringCommandNative("IFNAME=" + iface + " WPS_PIN any");
+ } else {
+ return doStringCommandNative("IFNAME=" + iface + " WPS_PIN " + bssid);
+ }
+ }
+ }
+
+ /* Configures an access point connection */
+ public boolean startWpsRegistrar(String bssid, String pin) {
+ if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false;
+ return doBooleanCommand("WPS_REG " + bssid + " " + pin);
+ }
+
+ public boolean cancelWps() {
+ return doBooleanCommand("WPS_CANCEL");
+ }
+
+ public boolean setPersistentReconnect(boolean enabled) {
+ int value = (enabled == true) ? 1 : 0;
+ return doBooleanCommand("SET persistent_reconnect " + value);
+ }
+
+ public boolean setDeviceName(String name) {
+ return doBooleanCommand("SET device_name " + name);
+ }
+
+ public boolean setDeviceType(String type) {
+ return doBooleanCommand("SET device_type " + type);
+ }
+
+ public boolean setConfigMethods(String cfg) {
+ return doBooleanCommand("SET config_methods " + cfg);
+ }
+
+ public boolean setManufacturer(String value) {
+ return doBooleanCommand("SET manufacturer " + value);
+ }
+
+ public boolean setModelName(String value) {
+ return doBooleanCommand("SET model_name " + value);
+ }
+
+ public boolean setModelNumber(String value) {
+ return doBooleanCommand("SET model_number " + value);
+ }
+
+ public boolean setSerialNumber(String value) {
+ return doBooleanCommand("SET serial_number " + value);
+ }
+
+ public boolean setP2pSsidPostfix(String postfix) {
+ return doBooleanCommand("SET p2p_ssid_postfix " + postfix);
+ }
+
+ public boolean setP2pGroupIdle(String iface, int time) {
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " SET p2p_group_idle " + time);
+ }
+ }
+
+ public void setPowerSave(boolean enabled) {
+ if (enabled) {
+ doBooleanCommand("SET ps 1");
+ } else {
+ doBooleanCommand("SET ps 0");
+ }
+ }
+
+ public boolean setP2pPowerSave(String iface, boolean enabled) {
+ synchronized (mLock) {
+ if (enabled) {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 1");
+ } else {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 0");
+ }
+ }
+ }
+
+ public boolean setWfdEnable(boolean enable) {
+ return doBooleanCommand("SET wifi_display " + (enable ? "1" : "0"));
+ }
+
+ public boolean setWfdDeviceInfo(String hex) {
+ return doBooleanCommand("WFD_SUBELEM_SET 0 " + hex);
+ }
+
+ /**
+ * "sta" prioritizes STA connection over P2P and "p2p" prioritizes
+ * P2P connection over STA
+ */
+ public boolean setConcurrencyPriority(String s) {
+ return doBooleanCommand("P2P_SET conc_pref " + s);
+ }
+
+ public boolean p2pFind() {
+ return doBooleanCommand("P2P_FIND");
+ }
+
+ public boolean p2pFind(int timeout) {
+ if (timeout <= 0) {
+ return p2pFind();
+ }
+ return doBooleanCommand("P2P_FIND " + timeout);
+ }
+
+ public boolean p2pStopFind() {
+ return doBooleanCommand("P2P_STOP_FIND");
+ }
+
+ public boolean p2pListen() {
+ return doBooleanCommand("P2P_LISTEN");
+ }
+
+ public boolean p2pListen(int timeout) {
+ if (timeout <= 0) {
+ return p2pListen();
+ }
+ return doBooleanCommand("P2P_LISTEN " + timeout);
+ }
+
+ public boolean p2pExtListen(boolean enable, int period, int interval) {
+ if (enable && interval < period) {
+ return false;
+ }
+ return doBooleanCommand("P2P_EXT_LISTEN"
+ + (enable ? (" " + period + " " + interval) : ""));
+ }
+
+ public boolean p2pSetChannel(int lc, int oc) {
+ if (DBG) Log.d(mTAG, "p2pSetChannel: lc="+lc+", oc="+oc);
+
+ if (lc >=1 && lc <= 11) {
+ if (!doBooleanCommand("P2P_SET listen_channel " + lc)) {
+ return false;
+ }
+ } else if (lc != 0) {
+ return false;
+ }
+
+ if (oc >= 1 && oc <= 165 ) {
+ int freq = (oc <= 14 ? 2407 : 5000) + oc * 5;
+ return doBooleanCommand("P2P_SET disallow_freq 1000-"
+ + (freq - 5) + "," + (freq + 5) + "-6000");
+ } else if (oc == 0) {
+ /* oc==0 disables "P2P_SET disallow_freq" (enables all freqs) */
+ return doBooleanCommand("P2P_SET disallow_freq \"\"");
+ }
+
+ return false;
+ }
+
+ public boolean p2pFlush() {
+ return doBooleanCommand("P2P_FLUSH");
+ }
+
+ /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad]
+ [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */
+ public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
+ if (config == null) return null;
+ List<String> args = new ArrayList<String>();
+ WpsInfo wps = config.wps;
+ args.add(config.deviceAddress);
+
+ switch (wps.setup) {
+ case WpsInfo.PBC:
+ args.add("pbc");
+ break;
+ case WpsInfo.DISPLAY:
+ if (TextUtils.isEmpty(wps.pin)) {
+ args.add("pin");
+ } else {
+ args.add(wps.pin);
+ }
+ args.add("display");
+ break;
+ case WpsInfo.KEYPAD:
+ args.add(wps.pin);
+ args.add("keypad");
+ break;
+ case WpsInfo.LABEL:
+ args.add(wps.pin);
+ args.add("label");
+ default:
+ break;
+ }
+
+ if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ args.add("persistent");
+ }
+
+ if (joinExistingGroup) {
+ args.add("join");
+ } else {
+ //TODO: This can be adapted based on device plugged in state and
+ //device battery state
+ int groupOwnerIntent = config.groupOwnerIntent;
+ if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
+ groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
+ }
+ args.add("go_intent=" + groupOwnerIntent);
+ }
+
+ String command = "P2P_CONNECT ";
+ for (String s : args) command += s + " ";
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pCancelConnect() {
+ return doBooleanCommand("P2P_CANCEL");
+ }
+
+ public boolean p2pProvisionDiscovery(WifiP2pConfig config) {
+ if (config == null) return false;
+
+ switch (config.wps.setup) {
+ case WpsInfo.PBC:
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " pbc");
+ case WpsInfo.DISPLAY:
+ //We are doing display, so provision discovery is keypad
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " keypad");
+ case WpsInfo.KEYPAD:
+ //We are doing keypad, so provision discovery is display
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " display");
+ default:
+ break;
+ }
+ return false;
+ }
+
+ public boolean p2pGroupAdd(boolean persistent) {
+ if (persistent) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent");
+ }
+ return doBooleanCommand("P2P_GROUP_ADD");
+ }
+
+ public boolean p2pGroupAdd(int netId) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId);
+ }
+
+ public boolean p2pGroupRemove(String iface) {
+ if (TextUtils.isEmpty(iface)) return false;
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_GROUP_REMOVE " + iface);
+ }
+ }
+
+ public boolean p2pReject(String deviceAddress) {
+ return doBooleanCommand("P2P_REJECT " + deviceAddress);
+ }
+
+ /* Invite a peer to a group */
+ public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
+ if (TextUtils.isEmpty(deviceAddress)) return false;
+
+ if (group == null) {
+ return doBooleanCommand("P2P_INVITE peer=" + deviceAddress);
+ } else {
+ return doBooleanCommand("P2P_INVITE group=" + group.getInterface()
+ + " peer=" + deviceAddress + " go_dev_addr=" + group.getOwner().deviceAddress);
+ }
+ }
+
+ /* Reinvoke a persistent connection */
+ public boolean p2pReinvoke(int netId, String deviceAddress) {
+ if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false;
+
+ return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
+ }
+
+ public String p2pGetSsid(String deviceAddress) {
+ return p2pGetParam(deviceAddress, "oper_ssid");
+ }
+
+ public String p2pGetDeviceAddress() {
+ String status = status();
+ if (status == null) return "";
+
+ String[] tokens = status.split("\n");
+ for (String token : tokens) {
+ if (token.startsWith("p2p_device_address=")) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ return nameValue[1];
+ }
+ }
+ return "";
+ }
+
+ public int getGroupCapability(String deviceAddress) {
+ int gc = 0;
+ if (TextUtils.isEmpty(deviceAddress)) return gc;
+ String peerInfo = p2pPeer(deviceAddress);
+ if (TextUtils.isEmpty(peerInfo)) return gc;
+
+ String[] tokens = peerInfo.split("\n");
+ for (String token : tokens) {
+ if (token.startsWith("group_capab=")) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ try {
+ return Integer.decode(nameValue[1]);
+ } catch(NumberFormatException e) {
+ return gc;
+ }
+ }
+ }
+ return gc;
+ }
+
+ public String p2pPeer(String deviceAddress) {
+ return doStringCommand("P2P_PEER " + deviceAddress);
+ }
+
+ private String p2pGetParam(String deviceAddress, String key) {
+ if (deviceAddress == null) return null;
+
+ String peerInfo = p2pPeer(deviceAddress);
+ if (peerInfo == null) return null;
+ String[] tokens= peerInfo.split("\n");
+
+ key += "=";
+ for (String token : tokens) {
+ if (token.startsWith(key)) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ return nameValue[1];
+ }
+ }
+ return null;
+ }
+
+ public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
+ * P2P_SERVICE_ADD upnp <version hex> <service>
+ *
+ * e.g)
+ * [Bonjour]
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
+ * 09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
+ *
+ * [UPnP]
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_ADD";
+ command += (" " + s);
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_DEL bonjour <query hexdump>
+ * P2P_SERVICE_DEL upnp <version hex> <service>
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_DEL ";
+
+ String[] data = s.split(" ");
+ if (data.length < 2) {
+ return false;
+ }
+ if ("upnp".equals(data[0])) {
+ command += s;
+ } else if ("bonjour".equals(data[0])) {
+ command += data[0];
+ command += (" " + data[1]);
+ } else {
+ return false;
+ }
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceFlush() {
+ return doBooleanCommand("P2P_SERVICE_FLUSH");
+ }
+
+ public String p2pServDiscReq(String addr, String query) {
+ String command = "P2P_SERV_DISC_REQ";
+ command += (" " + addr);
+ command += (" " + query);
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pServDiscCancelReq(String id) {
+ return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
+ }
+
+ /* Set the current mode of miracast operation.
+ * 0 = disabled
+ * 1 = operating as source
+ * 2 = operating as sink
+ */
+ public void setMiracastMode(int mode) {
+ // Note: optional feature on the driver. It is ok for this to fail.
+ doBooleanCommand("DRIVER MIRACAST " + mode);
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiNotificationController.java b/service/java/com/android/server/wifi/WifiNotificationController.java
new file mode 100644
index 000000000..ec6aa3776
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiNotificationController.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2013 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.server.wifi;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.TaskStackBuilder;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.NetworkInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/* Takes care of handling the "open wi-fi network available" notification @hide */
+final class WifiNotificationController {
+ /**
+ * The icon to show in the 'available networks' notification. This will also
+ * be the ID of the Notification given to the NotificationManager.
+ */
+ private static final int ICON_NETWORKS_AVAILABLE =
+ com.android.internal.R.drawable.stat_notify_wifi_in_range;
+ /**
+ * When a notification is shown, we wait this amount before possibly showing it again.
+ */
+ private final long NOTIFICATION_REPEAT_DELAY_MS;
+ /**
+ * Whether the user has set the setting to show the 'available networks' notification.
+ */
+ private boolean mNotificationEnabled;
+ /**
+ * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+ */
+ private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+ /**
+ * The {@link System#currentTimeMillis()} must be at least this value for us
+ * to show the notification again.
+ */
+ private long mNotificationRepeatTime;
+ /**
+ * The Notification object given to the NotificationManager.
+ */
+ private Notification mNotification;
+ /**
+ * Whether the notification is being shown, as set by us. That is, if the
+ * user cancels the notification, we will not receive the callback so this
+ * will still be true. We only guarantee if this is false, then the
+ * notification is not showing.
+ */
+ private boolean mNotificationShown;
+ /**
+ * The number of continuous scans that must occur before consider the
+ * supplicant in a scanning state. This allows supplicant to associate with
+ * remembered networks that are in the scan results.
+ */
+ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+ /**
+ * The number of scans since the last network state change. When this
+ * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+ * supplicant to actually be scanning. When the network state changes to
+ * something other than scanning, we reset this to 0.
+ */
+ private int mNumScansSinceNetworkStateChange;
+
+ private final Context mContext;
+ private final WifiStateMachine mWifiStateMachine;
+ private NetworkInfo mNetworkInfo;
+ private volatile int mWifiState;
+
+ WifiNotificationController(Context context, WifiStateMachine wsm) {
+ mContext = context;
+ mWifiStateMachine = wsm;
+ mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ resetNotification();
+ } else if (intent.getAction().equals(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ // reset & clear notification on a network connect & disconnect
+ switch(mNetworkInfo.getDetailedState()) {
+ case CONNECTED:
+ case DISCONNECTED:
+ case CAPTIVE_PORTAL_CHECK:
+ resetNotification();
+ break;
+ }
+ } else if (intent.getAction().equals(
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ checkAndSetNotification(mNetworkInfo,
+ mWifiStateMachine.syncGetScanResultsList());
+ }
+ }
+ }, filter);
+
+ // Setting is in seconds
+ NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+ mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+ mNotificationEnabledSettingObserver.register();
+ }
+
+ private synchronized void checkAndSetNotification(NetworkInfo networkInfo,
+ List<ScanResult> scanResults) {
+ // TODO: unregister broadcast so we do not have to check here
+ // If we shouldn't place a notification on available networks, then
+ // don't bother doing any of the following
+ if (!mNotificationEnabled) return;
+ if (networkInfo == null) return;
+ if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
+
+ NetworkInfo.State state = networkInfo.getState();
+ if ((state == NetworkInfo.State.DISCONNECTED)
+ || (state == NetworkInfo.State.UNKNOWN)) {
+ if (scanResults != null) {
+ int numOpenNetworks = 0;
+ for (int i = scanResults.size() - 1; i >= 0; i--) {
+ ScanResult scanResult = scanResults.get(i);
+
+ //A capability of [ESS] represents an open access point
+ //that is available for an STA to connect
+ if (scanResult.capabilities != null &&
+ scanResult.capabilities.equals("[ESS]")) {
+ numOpenNetworks++;
+ }
+ }
+
+ if (numOpenNetworks > 0) {
+ if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+ /*
+ * We've scanned continuously at least
+ * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+ * probably does not have a remembered network in range,
+ * since otherwise supplicant would have tried to
+ * associate and thus resetting this counter.
+ */
+ setNotificationVisible(true, numOpenNetworks, false, 0);
+ }
+ return;
+ }
+ }
+ }
+
+ // No open networks in range, remove the notification
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Clears variables related to tracking whether a notification has been
+ * shown recently and clears the current notification.
+ */
+ private synchronized void resetNotification() {
+ mNotificationRepeatTime = 0;
+ mNumScansSinceNetworkStateChange = 0;
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Display or don't display a notification that there are open Wi-Fi networks.
+ * @param visible {@code true} if notification should be visible, {@code false} otherwise
+ * @param numNetworks the number networks seen
+ * @param force {@code true} to force notification to be shown/not-shown,
+ * even if it is already shown/not-shown.
+ * @param delay time in milliseconds after which the notification should be made
+ * visible or invisible.
+ */
+ private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+ int delay) {
+
+ // Since we use auto cancel on the notification, when the
+ // mNetworksAvailableNotificationShown is true, the notification may
+ // have actually been canceled. However, when it is false we know
+ // for sure that it is not being shown (it will not be shown any other
+ // place than here)
+
+ // If it should be hidden and it is already hidden, then noop
+ if (!visible && !mNotificationShown && !force) {
+ return;
+ }
+
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Message message;
+ if (visible) {
+
+ // Not enough time has passed to show the notification again
+ if (System.currentTimeMillis() < mNotificationRepeatTime) {
+ return;
+ }
+
+ if (mNotification == null) {
+ // Cache the Notification object.
+ mNotification = new Notification();
+ mNotification.when = 0;
+ mNotification.icon = ICON_NETWORKS_AVAILABLE;
+ mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+ mNotification.contentIntent = TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(
+ new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
+ .getPendingIntent(0, 0, null, UserHandle.CURRENT);
+ }
+
+ CharSequence title = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available, numNetworks);
+ CharSequence details = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+ mNotification.tickerText = title;
+ mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+
+ mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
+
+ notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
+ UserHandle.ALL);
+ } else {
+ notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
+ }
+
+ mNotificationShown = visible;
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mNotificationEnabled " + mNotificationEnabled);
+ pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
+ pw.println("mNotificationShown " + mNotificationShown);
+ pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
+ }
+
+ private class NotificationEnabledSettingObserver extends ContentObserver {
+ public NotificationEnabledSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+ synchronized (WifiNotificationController.this) {
+ mNotificationEnabled = getValue();
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ synchronized (WifiNotificationController.this) {
+ mNotificationEnabled = getValue();
+ resetNotification();
+ }
+ }
+
+ private boolean getValue() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+ }
+ }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiService.java b/service/java/com/android/server/wifi/WifiService.java
new file mode 100644
index 000000000..3af9de699
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiService.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.wifi;
+
+import android.content.Context;
+import android.util.Log;
+import com.android.server.SystemService;
+
+public final class WifiService extends SystemService {
+
+ private static final String TAG = "WifiService";
+ WifiServiceImpl mImpl;
+
+ public WifiService() { }
+
+ @Override
+ public void onCreate(Context context) {
+ mImpl = new WifiServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "Registering " + Context.WIFI_SERVICE);
+ publishBinderService(Context.WIFI_SERVICE, mImpl);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mImpl.checkAndStartWifi();
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
new file mode 100644
index 000000000..897e55eff
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -0,0 +1,1591 @@
+/*
+ * 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.server.wifi;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.DhcpInfo;
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Messenger;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.os.AsyncTask;
+import android.provider.Settings;
+import android.util.Slog;
+
+import java.io.FileNotFoundException;
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.lang.Override;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+import com.android.server.SystemService;
+import com.android.server.am.BatteryStatsService;
+import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
+import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
+/**
+ * WifiService handles remote WiFi operation requests by implementing
+ * the IWifiManager interface.
+ *
+ * @hide
+ */
+public final class WifiServiceImpl extends IWifiManager.Stub {
+ private static final String TAG = "WifiService";
+ private static final boolean DBG = false;
+
+ final WifiStateMachine mWifiStateMachine;
+
+ private final Context mContext;
+
+ final LockList mLocks = new LockList();
+ // some wifi lock statistics
+ private int mFullHighPerfLocksAcquired;
+ private int mFullHighPerfLocksReleased;
+ private int mFullLocksAcquired;
+ private int mFullLocksReleased;
+ private int mScanLocksAcquired;
+ private int mScanLocksReleased;
+
+ private final List<Multicaster> mMulticasters =
+ new ArrayList<Multicaster>();
+ private int mMulticastEnabled;
+ private int mMulticastDisabled;
+
+ private final IBatteryStats mBatteryStats;
+ private final AppOpsManager mAppOps;
+
+ private String mInterfaceName;
+
+ /* Tracks the open wi-fi network notification */
+ private WifiNotificationController mNotificationController;
+ /* Polls traffic stats and notifies clients */
+ private WifiTrafficPoller mTrafficPoller;
+ /* Tracks the persisted states for wi-fi & airplane mode */
+ final WifiSettingsStore mSettingsStore;
+
+ final boolean mBatchedScanSupported;
+
+ /**
+ * Asynchronous channel to WifiStateMachine
+ */
+ private AsyncChannel mWifiStateMachineChannel;
+
+ /**
+ * Handles client connections
+ */
+ private class ClientHandler extends Handler {
+
+ ClientHandler(android.os.Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
+ // We track the clients by the Messenger
+ // since it is expected to be always available
+ mTrafficPoller.addClient(msg.replyTo);
+ } else {
+ Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+ if (DBG) Slog.d(TAG, "Send failed, client connection lost");
+ } else {
+ if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+ }
+ mTrafficPoller.removeClient(msg.replyTo);
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, this, msg.replyTo);
+ break;
+ }
+ /* Client commands are forwarded to state machine */
+ case WifiManager.CONNECT_NETWORK:
+ case WifiManager.SAVE_NETWORK: {
+ WifiConfiguration config = (WifiConfiguration) msg.obj;
+ int networkId = msg.arg1;
+ if (config != null && config.isValid()) {
+ // This is restricted because there is no UI for the user to
+ // monitor/control PAC.
+ if (config.proxySettings != ProxySettings.PAC) {
+ if (DBG) Slog.d(TAG, "Connect with config" + config);
+ mWifiStateMachine.sendMessage(Message.obtain(msg));
+ } else {
+ Slog.e(TAG, "ClientHandler.handleMessage cannot process msg with PAC");
+ if (msg.what == WifiManager.CONNECT_NETWORK) {
+ replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
+ } else {
+ replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
+ }
+ }
+ } else if (config == null
+ && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+ if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
+ mWifiStateMachine.sendMessage(Message.obtain(msg));
+ } else {
+ Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
+ if (msg.what == WifiManager.CONNECT_NETWORK) {
+ replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
+ } else {
+ replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
+ }
+ }
+ break;
+ }
+ case WifiManager.FORGET_NETWORK:
+ case WifiManager.START_WPS:
+ case WifiManager.CANCEL_WPS:
+ case WifiManager.DISABLE_NETWORK:
+ case WifiManager.RSSI_PKTCNT_FETCH: {
+ mWifiStateMachine.sendMessage(Message.obtain(msg));
+ break;
+ }
+ default: {
+ Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
+ break;
+ }
+ }
+ }
+
+ private void replyFailed(Message msg, int what) {
+ Message reply = msg.obtain();
+ reply.what = what;
+ reply.arg1 = WifiManager.INVALID_ARGS;
+ try {
+ msg.replyTo.send(reply);
+ } catch (RemoteException e) {
+ // There's not much we can do if reply can't be sent!
+ }
+ }
+ }
+ private ClientHandler mClientHandler;
+
+ /**
+ * Handles interaction with WifiStateMachine
+ */
+ private class WifiStateMachineHandler extends Handler {
+ private AsyncChannel mWsmChannel;
+
+ WifiStateMachineHandler(android.os.Looper looper) {
+ super(looper);
+ mWsmChannel = new AsyncChannel();
+ mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mWifiStateMachineChannel = mWsmChannel;
+ } else {
+ Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1);
+ mWifiStateMachineChannel = null;
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ Slog.e(TAG, "WifiStateMachine channel lost, msg.arg1 =" + msg.arg1);
+ mWifiStateMachineChannel = null;
+ //Re-establish connection to state machine
+ mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
+ break;
+ }
+ default: {
+ Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg);
+ break;
+ }
+ }
+ }
+ }
+
+ WifiStateMachineHandler mWifiStateMachineHandler;
+
+ private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
+
+ private WifiController mWifiController;
+
+ public WifiServiceImpl(Context context) {
+ mContext = context;
+
+ mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
+
+ mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
+ mWifiStateMachine.enableRssiPolling(true);
+ mBatteryStats = BatteryStatsService.getService();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+ mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
+ mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
+ mSettingsStore = new WifiSettingsStore(mContext);
+
+ HandlerThread wifiThread = new HandlerThread("WifiService");
+ wifiThread.start();
+ mClientHandler = new ClientHandler(wifiThread.getLooper());
+ mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
+ mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
+ mWifiController.start();
+
+ mBatchedScanSupported = mContext.getResources().getBoolean(
+ R.bool.config_wifi_batched_scan_supported);
+
+ registerForScanModeChange();
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mSettingsStore.handleAirplaneModeToggled()) {
+ mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
+ }
+ }
+ },
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+ // Adding optimizations of only receiving broadcasts when wifi is enabled
+ // can result in race conditions when apps toggle wifi in the background
+ // without active user involvement. Always receive broadcasts.
+ registerForBroadcasts();
+ }
+
+
+ /**
+ * Check if Wi-Fi needs to be enabled and start
+ * if needed
+ *
+ * This function is used only at boot time
+ */
+ public void checkAndStartWifi() {
+ /* Check if wi-fi needs to be enabled */
+ boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
+ Slog.i(TAG, "WifiService starting up with Wi-Fi " +
+ (wifiEnabled ? "enabled" : "disabled"));
+
+ // If we are already disabled (could be due to airplane mode), avoid changing persist
+ // state here
+ if (wifiEnabled) setWifiEnabled(wifiEnabled);
+
+ mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
+ makeWifiWatchdogStateMachine(mContext);
+
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#pingSupplicant()}
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ */
+ public boolean pingSupplicant() {
+ enforceAccessPermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return false;
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#startScan()}
+ *
+ * <p>If workSource is null, all blame is given to the calling uid.
+ */
+ public void startScan(WorkSource workSource) {
+ enforceChangePermission();
+ if (workSource != null) {
+ enforceWorkSourcePermission();
+ // WifiManager currently doesn't use names, so need to clear names out of the
+ // supplied WorkSource to allow future WorkSource combining.
+ workSource.clearNames();
+ }
+ mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
+ }
+
+ private class BatchedScanRequest extends DeathRecipient {
+ final BatchedScanSettings settings;
+ final int uid;
+ final int pid;
+ final WorkSource workSource;
+
+ BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
+ super(0, null, binder, null);
+ this.settings = settings;
+ this.uid = getCallingUid();
+ this.pid = getCallingPid();
+ workSource = ws;
+ }
+ public void binderDied() {
+ stopBatchedScan(settings, uid, pid);
+ }
+ public String toString() {
+ return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
+ }
+
+ public boolean isSameApp(int uid, int pid) {
+ return (this.uid == uid && this.pid == pid);
+ }
+ }
+
+ private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
+
+ public boolean isBatchedScanSupported() {
+ return mBatchedScanSupported;
+ }
+
+ public void pollBatchedScan() {
+ enforceChangePermission();
+ if (mBatchedScanSupported == false) return;
+ mWifiStateMachine.requestBatchedScanPoll();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
+ */
+ public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
+ WorkSource workSource) {
+ enforceChangePermission();
+ if (workSource != null) {
+ enforceWorkSourcePermission();
+ // WifiManager currently doesn't use names, so need to clear names out of the
+ // supplied WorkSource to allow future WorkSource combining.
+ workSource.clearNames();
+ }
+ if (mBatchedScanSupported == false) return false;
+ requested = new BatchedScanSettings(requested);
+ if (requested.isInvalid()) return false;
+ BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
+ synchronized(mBatchedScanners) {
+ mBatchedScanners.add(r);
+ resolveBatchedScannersLocked();
+ }
+ return true;
+ }
+
+ public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
+ enforceAccessPermission();
+ if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
+ int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return new ArrayList<BatchedScanResult>();
+ }
+ int currentUser = ActivityManager.getCurrentUser();
+ if (userId != currentUser) {
+ return new ArrayList<BatchedScanResult>();
+ } else {
+ return mWifiStateMachine.syncGetBatchedScanResultsList();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+
+ public void stopBatchedScan(BatchedScanSettings settings) {
+ enforceChangePermission();
+ if (mBatchedScanSupported == false) return;
+ stopBatchedScan(settings, getCallingUid(), getCallingPid());
+ }
+
+ private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) {
+ ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>();
+ synchronized(mBatchedScanners) {
+ for (BatchedScanRequest r : mBatchedScanners) {
+ if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) {
+ found.add(r);
+ if (settings != null) break;
+ }
+ }
+ for (BatchedScanRequest r : found) {
+ mBatchedScanners.remove(r);
+ }
+ if (found.size() != 0) {
+ resolveBatchedScannersLocked();
+ }
+ }
+ }
+
+ private void resolveBatchedScannersLocked() {
+ BatchedScanSettings setting = new BatchedScanSettings();
+ WorkSource responsibleWorkSource = null;
+ int responsibleUid = 0;
+ double responsibleCsph = 0; // Channel Scans Per Hour
+
+ if (mBatchedScanners.size() == 0) {
+ mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
+ return;
+ }
+ for (BatchedScanRequest r : mBatchedScanners) {
+ BatchedScanSettings s = r.settings;
+
+ // evaluate responsibility
+ int currentChannelCount;
+ int currentScanInterval;
+ double currentCsph;
+
+ if (s.channelSet == null || s.channelSet.isEmpty()) {
+ // all channels - 11 B and 9 A channels roughly.
+ currentChannelCount = 9 + 11;
+ } else {
+ currentChannelCount = s.channelSet.size();
+ // these are rough est - no real need to correct for reg-domain;
+ if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
+ if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
+
+ }
+ if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+ currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+ } else {
+ currentScanInterval = s.scanIntervalSec;
+ }
+ currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
+
+ if (currentCsph > responsibleCsph) {
+ responsibleUid = r.uid;
+ responsibleWorkSource = r.workSource;
+ responsibleCsph = currentCsph;
+ }
+
+ if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
+ s.maxScansPerBatch < setting.maxScansPerBatch) {
+ setting.maxScansPerBatch = s.maxScansPerBatch;
+ }
+ if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
+ (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
+ s.maxApPerScan > setting.maxApPerScan)) {
+ setting.maxApPerScan = s.maxApPerScan;
+ }
+ if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
+ s.scanIntervalSec < setting.scanIntervalSec) {
+ setting.scanIntervalSec = s.scanIntervalSec;
+ }
+ if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
+ (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
+ s.maxApForDistance > setting.maxApForDistance)) {
+ setting.maxApForDistance = s.maxApForDistance;
+ }
+ if (s.channelSet != null && s.channelSet.size() != 0) {
+ if (setting.channelSet == null || setting.channelSet.size() != 0) {
+ if (setting.channelSet == null) setting.channelSet = new ArrayList<String>();
+ for (String i : s.channelSet) {
+ if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
+ }
+ } // else, ignore the constraint - we already use all channels
+ } else {
+ if (setting.channelSet == null || setting.channelSet.size() != 0) {
+ setting.channelSet = new ArrayList<String>();
+ }
+ }
+ }
+
+ setting.constrain();
+ mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
+ responsibleWorkSource);
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+ "WifiService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+ "WifiService");
+
+ }
+
+ private void enforceWorkSourcePermission() {
+ mContext.enforceCallingPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+ "WifiService");
+
+ }
+
+ private void enforceMulticastChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+ "WifiService");
+ }
+
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "ConnectivityService");
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
+ * @param enable {@code true} to enable, {@code false} to disable.
+ * @return {@code true} if the enable/disable operation was
+ * started or is already in the queue.
+ */
+ public synchronized boolean setWifiEnabled(boolean enable) {
+ enforceChangePermission();
+ Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ if (DBG) {
+ Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
+ }
+
+ /*
+ * Caller might not have WRITE_SECURE_SETTINGS,
+ * only CHANGE_WIFI_STATE is enforced
+ */
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (! mSettingsStore.handleWifiToggled(enable)) {
+ // Nothing to do if wifi cannot be toggled
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ mWifiController.sendMessage(CMD_WIFI_TOGGLED);
+ return true;
+ }
+
+ /**
+ * see {@link WifiManager#getWifiState()}
+ * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
+ * {@link WifiManager#WIFI_STATE_DISABLING},
+ * {@link WifiManager#WIFI_STATE_ENABLED},
+ * {@link WifiManager#WIFI_STATE_ENABLING},
+ * {@link WifiManager#WIFI_STATE_UNKNOWN}
+ */
+ public int getWifiEnabledState() {
+ enforceAccessPermission();
+ return mWifiStateMachine.syncGetWifiState();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
+ * @param wifiConfig SSID, security and channel details as
+ * part of WifiConfiguration
+ * @param enabled true to enable and false to disable
+ */
+ public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+ enforceChangePermission();
+ // null wifiConfig is a meaningful input for CMD_SET_AP
+ if (wifiConfig == null || wifiConfig.isValid()) {
+ mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
+ } else {
+ Slog.e(TAG, "Invalid WifiConfiguration");
+ }
+ }
+
+ /**
+ * see {@link WifiManager#getWifiApState()}
+ * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+ * {@link WifiManager#WIFI_AP_STATE_DISABLING},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLED},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLING},
+ * {@link WifiManager#WIFI_AP_STATE_FAILED}
+ */
+ public int getWifiApEnabledState() {
+ enforceAccessPermission();
+ return mWifiStateMachine.syncGetWifiApState();
+ }
+
+ /**
+ * see {@link WifiManager#getWifiApConfiguration()}
+ * @return soft access point configuration
+ */
+ public WifiConfiguration getWifiApConfiguration() {
+ enforceAccessPermission();
+ return mWifiStateMachine.syncGetWifiApConfiguration();
+ }
+
+ /**
+ * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
+ * @param wifiConfig WifiConfiguration details for soft access point
+ */
+ public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+ enforceChangePermission();
+ if (wifiConfig == null)
+ return;
+ if (wifiConfig.isValid()) {
+ mWifiStateMachine.setWifiApConfiguration(wifiConfig);
+ } else {
+ Slog.e(TAG, "Invalid WifiConfiguration");
+ }
+ }
+
+ /**
+ * @param enable {@code true} to enable, {@code false} to disable.
+ * @return {@code true} if the enable/disable operation was
+ * started or is already in the queue.
+ */
+ public boolean isScanAlwaysAvailable() {
+ enforceAccessPermission();
+ return mSettingsStore.isScanAlwaysAvailable();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#disconnect()}
+ */
+ public void disconnect() {
+ enforceChangePermission();
+ mWifiStateMachine.disconnectCommand();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#reconnect()}
+ */
+ public void reconnect() {
+ enforceChangePermission();
+ mWifiStateMachine.reconnectCommand();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#reassociate()}
+ */
+ public void reassociate() {
+ enforceChangePermission();
+ mWifiStateMachine.reassociateCommand();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
+ * @return the list of configured networks
+ */
+ public List<WifiConfiguration> getConfiguredNetworks() {
+ enforceAccessPermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return null;
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
+ * @return the supplicant-assigned identifier for the new or updated
+ * network if the operation succeeds, or {@code -1} if it fails
+ */
+ public int addOrUpdateNetwork(WifiConfiguration config) {
+ enforceChangePermission();
+ if (config.proxySettings == ProxySettings.PAC) {
+ enforceConnectivityInternalPermission();
+ }
+ if (config.isValid()) {
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return -1;
+ }
+ } else {
+ Slog.e(TAG, "bad network configuration");
+ return -1;
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean removeNetwork(int netId) {
+ enforceChangePermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return false;
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @param disableOthers if true, disable all other networks.
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ enforceChangePermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
+ disableOthers);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return false;
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#disableNetwork(int)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean disableNetwork(int netId) {
+ enforceChangePermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return false;
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
+ * @return the Wi-Fi information, contained in {@link WifiInfo}.
+ */
+ public WifiInfo getConnectionInfo() {
+ enforceAccessPermission();
+ /*
+ * Make sure we have the latest information, by sending
+ * a status request to the supplicant.
+ */
+ return mWifiStateMachine.syncRequestConnectionInfo();
+ }
+
+ /**
+ * Return the results of the most recent access point scan, in the form of
+ * a list of {@link ScanResult} objects.
+ * @return the list of results
+ */
+ public List<ScanResult> getScanResults(String callingPackage) {
+ enforceAccessPermission();
+ int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return new ArrayList<ScanResult>();
+ }
+ int currentUser = ActivityManager.getCurrentUser();
+ if (userId != currentUser) {
+ return new ArrayList<ScanResult>();
+ } else {
+ return mWifiStateMachine.syncGetScanResultsList();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Tell the supplicant to persist the current list of configured networks.
+ * @return {@code true} if the operation succeeded
+ *
+ * TODO: deprecate this
+ */
+ public boolean saveConfiguration() {
+ boolean result = true;
+ enforceChangePermission();
+ if (mWifiStateMachineChannel != null) {
+ return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
+ } else {
+ Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+ return false;
+ }
+ }
+
+ /**
+ * Set the country code
+ * @param countryCode ISO 3166 country code.
+ * @param persist {@code true} if the setting should be remembered.
+ *
+ * The persist behavior exists so that wifi can fall back to the last
+ * persisted country code on a restart, when the locale information is
+ * not available from telephony.
+ */
+ public void setCountryCode(String countryCode, boolean persist) {
+ Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
+ " with persist set to " + persist);
+ enforceChangePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mWifiStateMachine.setCountryCode(countryCode, persist);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the operational frequency band
+ * @param band One of
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
+ * @param persist {@code true} if the setting should be remembered.
+ *
+ */
+ public void setFrequencyBand(int band, boolean persist) {
+ enforceChangePermission();
+ if (!isDualBandSupported()) return;
+ Slog.i(TAG, "WifiService trying to set frequency band to " + band +
+ " with persist set to " + persist);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mWifiStateMachine.setFrequencyBand(band, persist);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+
+ /**
+ * Get the operational frequency band
+ */
+ public int getFrequencyBand() {
+ enforceAccessPermission();
+ return mWifiStateMachine.getFrequencyBand();
+ }
+
+ public boolean isDualBandSupported() {
+ //TODO: Should move towards adding a driver API that checks at runtime
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_wifi_dual_band_support);
+ }
+
+ /**
+ * Return the DHCP-assigned addresses from the last successful DHCP request,
+ * if any.
+ * @return the DHCP information
+ * @deprecated
+ */
+ public DhcpInfo getDhcpInfo() {
+ enforceAccessPermission();
+ DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
+ if (dhcpResults.linkProperties == null) return null;
+
+ DhcpInfo info = new DhcpInfo();
+ for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
+ InetAddress addr = la.getAddress();
+ if (addr instanceof Inet4Address) {
+ info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
+ break;
+ }
+ }
+ for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
+ if (r.isDefaultRoute()) {
+ InetAddress gateway = r.getGateway();
+ if (gateway instanceof Inet4Address) {
+ info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
+ }
+ } else if (r.hasGateway() == false) {
+ LinkAddress dest = r.getDestination();
+ if (dest.getAddress() instanceof Inet4Address) {
+ info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
+ dest.getNetworkPrefixLength());
+ }
+ }
+ }
+ int dnsFound = 0;
+ for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
+ if (dns instanceof Inet4Address) {
+ if (dnsFound == 0) {
+ info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+ } else {
+ info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+ }
+ if (++dnsFound > 1) break;
+ }
+ }
+ InetAddress serverAddress = dhcpResults.serverAddress;
+ if (serverAddress instanceof Inet4Address) {
+ info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
+ }
+ info.leaseDuration = dhcpResults.leaseDuration;
+
+ return info;
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#startWifi}
+ *
+ */
+ public void startWifi() {
+ enforceConnectivityInternalPermission();
+ /* TODO: may be add permissions for access only to connectivity service
+ * TODO: if a start issued, keep wifi alive until a stop issued irrespective
+ * of WifiLock & device idle status unless wifi enabled status is toggled
+ */
+
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.reconnectCommand();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#stopWifi}
+ *
+ */
+ public void stopWifi() {
+ enforceConnectivityInternalPermission();
+ /*
+ * TODO: if a stop is issued, wifi is brought up only by startWifi
+ * unless wifi enabled status is toggled
+ */
+ mWifiStateMachine.setDriverStart(false);
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#addToBlacklist}
+ *
+ */
+ public void addToBlacklist(String bssid) {
+ enforceChangePermission();
+
+ mWifiStateMachine.addToBlacklist(bssid);
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#clearBlacklist}
+ *
+ */
+ public void clearBlacklist() {
+ enforceChangePermission();
+
+ mWifiStateMachine.clearBlacklist();
+ }
+
+ /**
+ * enable TDLS for the local NIC to remote NIC
+ * The APPs don't know the remote MAC address to identify NIC though,
+ * so we need to do additional work to find it from remote IP address
+ */
+
+ class TdlsTaskParams {
+ public String remoteIpAddress;
+ public boolean enable;
+ }
+
+ class TdlsTask extends AsyncTask<TdlsTaskParams, Integer, Integer> {
+ @Override
+ protected Integer doInBackground(TdlsTaskParams... params) {
+
+ // Retrieve parameters for the call
+ TdlsTaskParams param = params[0];
+ String remoteIpAddress = param.remoteIpAddress.trim();
+ boolean enable = param.enable;
+
+ // Get MAC address of Remote IP
+ String macAddress = null;
+
+ BufferedReader reader = null;
+
+ try {
+ reader = new BufferedReader(new FileReader("/proc/net/arp"));
+
+ // Skip over the line bearing colum titles
+ String line = reader.readLine();
+
+ while ((line = reader.readLine()) != null) {
+ String[] tokens = line.split("[ ]+");
+ if (tokens.length < 6) {
+ continue;
+ }
+
+ // ARP column format is
+ // Address HWType HWAddress Flags Mask IFace
+ String ip = tokens[0];
+ String mac = tokens[3];
+
+ if (remoteIpAddress.equals(ip)) {
+ macAddress = mac;
+ break;
+ }
+ }
+
+ if (macAddress == null) {
+ Slog.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " +
+ "/proc/net/arp");
+ } else {
+ enableTdlsWithMacAddress(macAddress, enable);
+ }
+
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Could not open /proc/net/arp to lookup mac address");
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not read /proc/net/arp to lookup mac address");
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ catch (IOException e) {
+ // Do nothing
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ public void enableTdls(String remoteAddress, boolean enable) {
+ TdlsTaskParams params = new TdlsTaskParams();
+ params.remoteIpAddress = remoteAddress;
+ params.enable = enable;
+ new TdlsTask().execute(params);
+ }
+
+
+ public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
+ mWifiStateMachine.enableTdls(remoteMacAddress, enable);
+ }
+
+ /**
+ * Get a reference to handler. This is used by a client to establish
+ * an AsyncChannel communication with WifiService
+ */
+ public Messenger getWifiServiceMessenger() {
+ enforceAccessPermission();
+ enforceChangePermission();
+ return new Messenger(mClientHandler);
+ }
+
+ /** Get a reference to WifiStateMachine handler for AsyncChannel communication */
+ public Messenger getWifiStateMachineMessenger() {
+ enforceAccessPermission();
+ enforceChangePermission();
+ return mWifiStateMachine.getMessenger();
+ }
+
+ /**
+ * Get the IP and proxy configuration file
+ */
+ public String getConfigFile() {
+ enforceAccessPermission();
+ return mWifiStateMachine.getConfigFile();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ mWifiController.sendMessage(CMD_SCREEN_ON);
+ } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
+ mWifiController.sendMessage(CMD_USER_PRESENT);
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ mWifiController.sendMessage(CMD_SCREEN_OFF);
+ } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ int pluggedType = intent.getIntExtra("plugged", 0);
+ mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
+ } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ BluetoothAdapter.STATE_DISCONNECTED);
+ mWifiStateMachine.sendBluetoothAdapterStateChange(state);
+ } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+ boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
+ mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
+ }
+ }
+ };
+
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForScanModeChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mSettingsStore.handleWifiScanAlwaysAvailableToggled();
+ mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
+ false, contentObserver);
+ }
+
+ private void registerForBroadcasts() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_USER_PRESENT);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+ intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ mContext.registerReceiver(mReceiver, intentFilter);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump WifiService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
+ pw.println("Stay-awake conditions: " +
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
+ pw.println("mMulticastEnabled " + mMulticastEnabled);
+ pw.println("mMulticastDisabled " + mMulticastDisabled);
+ mWifiController.dump(fd, pw, args);
+ mSettingsStore.dump(fd, pw, args);
+ mNotificationController.dump(fd, pw, args);
+ mTrafficPoller.dump(fd, pw, args);
+
+ pw.println("Latest scan results:");
+ List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
+ if (scanResults != null && scanResults.size() != 0) {
+ pw.println(" BSSID Frequency RSSI Flags SSID");
+ for (ScanResult r : scanResults) {
+ pw.printf(" %17s %9d %5d %-16s %s%n",
+ r.BSSID,
+ r.frequency,
+ r.level,
+ r.capabilities,
+ r.SSID == null ? "" : r.SSID);
+ }
+ }
+ pw.println();
+ pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
+ mFullHighPerfLocksAcquired + " full high perf, " +
+ mScanLocksAcquired + " scan");
+ pw.println("Locks released: " + mFullLocksReleased + " full, " +
+ mFullHighPerfLocksReleased + " full high perf, " +
+ mScanLocksReleased + " scan");
+ pw.println();
+ pw.println("Locks held:");
+ mLocks.dump(pw);
+
+ mWifiWatchdogStateMachine.dump(fd, pw, args);
+ pw.println();
+ mWifiStateMachine.dump(fd, pw, args);
+ pw.println();
+ }
+
+ private class WifiLock extends DeathRecipient {
+ WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+ super(lockMode, tag, binder, ws);
+ }
+
+ public void binderDied() {
+ synchronized (mLocks) {
+ releaseWifiLockLocked(mBinder);
+ }
+ }
+
+ public String toString() {
+ return "WifiLock{" + mTag + " type=" + mMode + " binder=" + mBinder + "}";
+ }
+ }
+
+ class LockList {
+ private List<WifiLock> mList;
+
+ private LockList() {
+ mList = new ArrayList<WifiLock>();
+ }
+
+ synchronized boolean hasLocks() {
+ return !mList.isEmpty();
+ }
+
+ synchronized int getStrongestLockMode() {
+ if (mList.isEmpty()) {
+ return WifiManager.WIFI_MODE_FULL;
+ }
+
+ if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
+ return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+ }
+
+ if (mFullLocksAcquired > mFullLocksReleased) {
+ return WifiManager.WIFI_MODE_FULL;
+ }
+
+ return WifiManager.WIFI_MODE_SCAN_ONLY;
+ }
+
+ synchronized void updateWorkSource(WorkSource ws) {
+ for (int i = 0; i < mLocks.mList.size(); i++) {
+ ws.add(mLocks.mList.get(i).mWorkSource);
+ }
+ }
+
+ private void addLock(WifiLock lock) {
+ if (findLockByBinder(lock.mBinder) < 0) {
+ mList.add(lock);
+ }
+ }
+
+ private WifiLock removeLock(IBinder binder) {
+ int index = findLockByBinder(binder);
+ if (index >= 0) {
+ WifiLock ret = mList.remove(index);
+ ret.unlinkDeathRecipient();
+ return ret;
+ } else {
+ return null;
+ }
+ }
+
+ private int findLockByBinder(IBinder binder) {
+ int size = mList.size();
+ for (int i = size - 1; i >= 0; i--) {
+ if (mList.get(i).mBinder == binder)
+ return i;
+ }
+ return -1;
+ }
+
+ private void dump(PrintWriter pw) {
+ for (WifiLock l : mList) {
+ pw.print(" ");
+ pw.println(l);
+ }
+ }
+ }
+
+ void enforceWakeSourcePermission(int uid, int pid) {
+ if (uid == android.os.Process.myUid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+ pid, uid, null);
+ }
+
+ public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+ if (lockMode != WifiManager.WIFI_MODE_FULL &&
+ lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
+ lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
+ Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
+ if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
+ return false;
+ }
+ if (ws != null && ws.size() == 0) {
+ ws = null;
+ }
+ if (ws != null) {
+ enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ if (ws == null) {
+ ws = new WorkSource(Binder.getCallingUid());
+ }
+ WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
+ synchronized (mLocks) {
+ return acquireWifiLockLocked(wifiLock);
+ }
+ }
+
+ private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
+ switch(wifiLock.mMode) {
+ case WifiManager.WIFI_MODE_FULL:
+ case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+ case WifiManager.WIFI_MODE_SCAN_ONLY:
+ mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+ break;
+ }
+ }
+
+ private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
+ switch(wifiLock.mMode) {
+ case WifiManager.WIFI_MODE_FULL:
+ case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+ case WifiManager.WIFI_MODE_SCAN_ONLY:
+ mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+ break;
+ }
+ }
+
+ private boolean acquireWifiLockLocked(WifiLock wifiLock) {
+ if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
+
+ mLocks.addLock(wifiLock);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ noteAcquireWifiLock(wifiLock);
+ switch(wifiLock.mMode) {
+ case WifiManager.WIFI_MODE_FULL:
+ ++mFullLocksAcquired;
+ break;
+ case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+ ++mFullHighPerfLocksAcquired;
+ break;
+
+ case WifiManager.WIFI_MODE_SCAN_ONLY:
+ ++mScanLocksAcquired;
+ break;
+ }
+ mWifiController.sendMessage(CMD_LOCKS_CHANGED);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ if (ws != null && ws.size() == 0) {
+ ws = null;
+ }
+ if (ws != null) {
+ enforceWakeSourcePermission(uid, pid);
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLocks) {
+ int index = mLocks.findLockByBinder(lock);
+ if (index < 0) {
+ throw new IllegalArgumentException("Wifi lock not active");
+ }
+ WifiLock wl = mLocks.mList.get(index);
+ noteReleaseWifiLock(wl);
+ wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
+ noteAcquireWifiLock(wl);
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public boolean releaseWifiLock(IBinder lock) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+ synchronized (mLocks) {
+ return releaseWifiLockLocked(lock);
+ }
+ }
+
+ private boolean releaseWifiLockLocked(IBinder lock) {
+ boolean hadLock;
+
+ WifiLock wifiLock = mLocks.removeLock(lock);
+
+ if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
+
+ hadLock = (wifiLock != null);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (hadLock) {
+ noteReleaseWifiLock(wifiLock);
+ switch(wifiLock.mMode) {
+ case WifiManager.WIFI_MODE_FULL:
+ ++mFullLocksReleased;
+ break;
+ case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+ ++mFullHighPerfLocksReleased;
+ break;
+ case WifiManager.WIFI_MODE_SCAN_ONLY:
+ ++mScanLocksReleased;
+ break;
+ }
+ mWifiController.sendMessage(CMD_LOCKS_CHANGED);
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ return hadLock;
+ }
+
+ private abstract class DeathRecipient
+ implements IBinder.DeathRecipient {
+ String mTag;
+ int mMode;
+ IBinder mBinder;
+ WorkSource mWorkSource;
+
+ DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
+ super();
+ mTag = tag;
+ mMode = mode;
+ mBinder = binder;
+ mWorkSource = ws;
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ void unlinkDeathRecipient() {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ }
+
+ private class Multicaster extends DeathRecipient {
+ Multicaster(String tag, IBinder binder) {
+ super(Binder.getCallingUid(), tag, binder, null);
+ }
+
+ public void binderDied() {
+ Slog.e(TAG, "Multicaster binderDied");
+ synchronized (mMulticasters) {
+ int i = mMulticasters.indexOf(this);
+ if (i != -1) {
+ removeMulticasterLocked(i, mMode);
+ }
+ }
+ }
+
+ public String toString() {
+ return "Multicaster{" + mTag + " binder=" + mBinder + "}";
+ }
+
+ public int getUid() {
+ return mMode;
+ }
+ }
+
+ public void initializeMulticastFiltering() {
+ enforceMulticastChangePermission();
+
+ synchronized (mMulticasters) {
+ // if anybody had requested filters be off, leave off
+ if (mMulticasters.size() != 0) {
+ return;
+ } else {
+ mWifiStateMachine.startFilteringMulticastV4Packets();
+ }
+ }
+ }
+
+ public void acquireMulticastLock(IBinder binder, String tag) {
+ enforceMulticastChangePermission();
+
+ synchronized (mMulticasters) {
+ mMulticastEnabled++;
+ mMulticasters.add(new Multicaster(tag, binder));
+ // Note that we could call stopFilteringMulticastV4Packets only when
+ // our new size == 1 (first call), but this function won't
+ // be called often and by making the stopPacket call each
+ // time we're less fragile and self-healing.
+ mWifiStateMachine.stopFilteringMulticastV4Packets();
+ }
+
+ int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mBatteryStats.noteWifiMulticastEnabled(uid);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void releaseMulticastLock() {
+ enforceMulticastChangePermission();
+
+ int uid = Binder.getCallingUid();
+ synchronized (mMulticasters) {
+ mMulticastDisabled++;
+ int size = mMulticasters.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Multicaster m = mMulticasters.get(i);
+ if ((m != null) && (m.getUid() == uid)) {
+ removeMulticasterLocked(i, uid);
+ }
+ }
+ }
+ }
+
+ private void removeMulticasterLocked(int i, int uid)
+ {
+ Multicaster removed = mMulticasters.remove(i);
+
+ if (removed != null) {
+ removed.unlinkDeathRecipient();
+ }
+ if (mMulticasters.size() == 0) {
+ mWifiStateMachine.startFilteringMulticastV4Packets();
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mBatteryStats.noteWifiMulticastDisabled(uid);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public boolean isMulticastEnabled() {
+ enforceAccessPermission();
+
+ synchronized (mMulticasters) {
+ return (mMulticasters.size() > 0);
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiSettingsStore.java b/service/java/com/android/server/wifi/WifiSettingsStore.java
new file mode 100644
index 000000000..3ff806161
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiSettingsStore.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 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.server.wifi;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/* Tracks persisted settings for Wi-Fi and airplane mode interaction */
+final class WifiSettingsStore {
+ /* Values tracked in Settings.Global.WIFI_ON */
+ private static final int WIFI_DISABLED = 0;
+ private static final int WIFI_ENABLED = 1;
+ /* Wifi enabled while in airplane mode */
+ private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE = 2;
+ /* Wifi disabled due to airplane mode on */
+ private static final int WIFI_DISABLED_AIRPLANE_ON = 3;
+
+ /* Persisted state that tracks the wifi & airplane interaction from settings */
+ private int mPersistWifiState = WIFI_DISABLED;
+ /* Tracks current airplane mode state */
+ private boolean mAirplaneModeOn = false;
+
+ /* Tracks the setting of scan being available even when wi-fi is turned off
+ */
+ private boolean mScanAlwaysAvailable;
+
+ private final Context mContext;
+
+ /* Tracks if we have checked the saved wi-fi state after boot */
+ private boolean mCheckSavedStateAtBoot = false;
+
+ WifiSettingsStore(Context context) {
+ mContext = context;
+ mAirplaneModeOn = getPersistedAirplaneModeOn();
+ mPersistWifiState = getPersistedWifiState();
+ mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+ }
+
+ synchronized boolean isWifiToggleEnabled() {
+ if (!mCheckSavedStateAtBoot) {
+ mCheckSavedStateAtBoot = true;
+ if (testAndClearWifiSavedState()) return true;
+ }
+
+ if (mAirplaneModeOn) {
+ return mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE;
+ } else {
+ return mPersistWifiState != WIFI_DISABLED;
+ }
+ }
+
+ /**
+ * Returns true if airplane mode is currently on.
+ * @return {@code true} if airplane mode is on.
+ */
+ synchronized boolean isAirplaneModeOn() {
+ return mAirplaneModeOn;
+ }
+
+ synchronized boolean isScanAlwaysAvailable() {
+ return mScanAlwaysAvailable;
+ }
+
+ synchronized boolean handleWifiToggled(boolean wifiEnabled) {
+ // Can Wi-Fi be toggled in airplane mode ?
+ if (mAirplaneModeOn && !isAirplaneToggleable()) {
+ return false;
+ }
+
+ if (wifiEnabled) {
+ if (mAirplaneModeOn) {
+ persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
+ } else {
+ persistWifiState(WIFI_ENABLED);
+ }
+ } else {
+ // When wifi state is disabled, we do not care
+ // if airplane mode is on or not. The scenario of
+ // wifi being disabled due to airplane mode being turned on
+ // is handled handleAirplaneModeToggled()
+ persistWifiState(WIFI_DISABLED);
+ }
+ return true;
+ }
+
+ synchronized boolean handleAirplaneModeToggled() {
+ // Is Wi-Fi sensitive to airplane mode changes ?
+ if (!isAirplaneSensitive()) {
+ return false;
+ }
+
+ mAirplaneModeOn = getPersistedAirplaneModeOn();
+ if (mAirplaneModeOn) {
+ // Wifi disabled due to airplane on
+ if (mPersistWifiState == WIFI_ENABLED) {
+ persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
+ }
+ } else {
+ /* On airplane mode disable, restore wifi state if necessary */
+ if (testAndClearWifiSavedState() ||
+ mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
+ persistWifiState(WIFI_ENABLED);
+ }
+ }
+ return true;
+ }
+
+ synchronized void handleWifiScanAlwaysAvailableToggled() {
+ mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mPersistWifiState " + mPersistWifiState);
+ pw.println("mAirplaneModeOn " + mAirplaneModeOn);
+ }
+
+ private void persistWifiState(int state) {
+ final ContentResolver cr = mContext.getContentResolver();
+ mPersistWifiState = state;
+ Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);
+ }
+
+ /* Does Wi-Fi need to be disabled when airplane mode is on ? */
+ private boolean isAirplaneSensitive() {
+ String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_RADIOS);
+ return airplaneModeRadios == null
+ || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI);
+ }
+
+ /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */
+ private boolean isAirplaneToggleable() {
+ String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ return toggleableRadios != null
+ && toggleableRadios.contains(Settings.Global.RADIO_WIFI);
+ }
+
+ /* After a reboot, we restore wi-fi to be on if it was turned off temporarily for tethering.
+ * The settings app tracks the saved state, but the framework has to check it at boot to
+ * make sure the wi-fi is turned on in case it was turned off for the purpose of tethering.
+ *
+ * Note that this is not part of the regular WIFI_ON setting because this only needs to
+ * be controlled through the settings app and not the Wi-Fi public API.
+ */
+ private boolean testAndClearWifiSavedState() {
+ final ContentResolver cr = mContext.getContentResolver();
+ int wifiSavedState = 0;
+ try {
+ wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE);
+ if(wifiSavedState == 1)
+ Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+ } catch (Settings.SettingNotFoundException e) {
+ ;
+ }
+ return (wifiSavedState == 1);
+ }
+
+ private int getPersistedWifiState() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Global.getInt(cr, Settings.Global.WIFI_ON);
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED);
+ return WIFI_DISABLED;
+ }
+ }
+
+ private boolean getPersistedAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ private boolean getPersistedScanAlwaysAvailable() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+ 0) == 1;
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
new file mode 100644
index 000000000..08621fc41
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -0,0 +1,4419 @@
+/*
+ * 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.server.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+
+/**
+ * TODO:
+ * Deprecate WIFI_STATE_UNKNOWN
+ */
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.backup.IBackupManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.DhcpStateMachine;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.RssiPacketCountInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.WpsResult;
+import android.net.wifi.WpsResult.Status;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.LruCache;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.p2p.WifiP2pServiceImpl;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+/**
+ * Track the state of Wifi connectivity. All event handling is done here,
+ * and all changes in connectivity state are initiated here.
+ *
+ * Wi-Fi now supports three modes of operation: Client, SoftAp and p2p
+ * In the current implementation, we support concurrent wifi p2p and wifi operation.
+ * The WifiStateMachine handles SoftAp and Client operations while WifiP2pService
+ * handles p2p operation.
+ *
+ * @hide
+ */
+public class WifiStateMachine extends StateMachine {
+
+ private static final String NETWORKTYPE = "WIFI";
+ private static final boolean DBG = false;
+
+ private WifiMonitor mWifiMonitor;
+ private WifiNative mWifiNative;
+ private WifiConfigStore mWifiConfigStore;
+ private INetworkManagementService mNwService;
+ private ConnectivityManager mCm;
+
+ private final boolean mP2pSupported;
+ private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
+ private boolean mTemporarilyDisconnectWifi = false;
+ private final String mPrimaryDeviceType;
+
+ /* Scan results handling */
+ private List<ScanResult> mScanResults = new ArrayList<ScanResult>();
+ private static final Pattern scanResultPattern = Pattern.compile("\t+");
+ private static final int SCAN_RESULT_CACHE_SIZE = 80;
+ private final LruCache<String, ScanResult> mScanResultCache;
+
+ /* Batch scan results */
+ private final List<BatchedScanResult> mBatchedScanResults =
+ new ArrayList<BatchedScanResult>();
+ private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
+ private int mExpectedBatchedScans = 0;
+ private long mBatchedScanMinPollTime = 0;
+
+ /* Chipset supports background scan */
+ private final boolean mBackgroundScanSupported;
+
+ private String mInterfaceName;
+ /* Tethering interface could be separate from wlan interface */
+ private String mTetherInterfaceName;
+
+ private int mLastSignalLevel = -1;
+ private String mLastBssid;
+ private int mLastNetworkId;
+ private boolean mEnableRssiPolling = false;
+ private boolean mEnableBackgroundScan = false;
+ private int mRssiPollToken = 0;
+ private int mReconnectCount = 0;
+ /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
+ * In CONNECT_MODE, the STA can scan and connect to an access point
+ * In SCAN_ONLY_MODE, the STA can only scan for access points
+ * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
+ */
+ private int mOperationalMode = CONNECT_MODE;
+ private boolean mScanResultIsPending = false;
+ private WorkSource mScanWorkSource = null;
+ private static final int UNKNOWN_SCAN_SOURCE = -1;
+ /* Tracks if state machine has received any screen state change broadcast yet.
+ * We can miss one of these at boot.
+ */
+ private AtomicBoolean mScreenBroadcastReceived = new AtomicBoolean(false);
+
+ private boolean mBluetoothConnectionActive = false;
+
+ private PowerManager.WakeLock mSuspendWakeLock;
+
+ /**
+ * Interval in milliseconds between polling for RSSI
+ * and linkspeed information
+ */
+ private static final int POLL_RSSI_INTERVAL_MSECS = 3000;
+
+ /**
+ * Delay between supplicant restarts upon failure to establish connection
+ */
+ private static final int SUPPLICANT_RESTART_INTERVAL_MSECS = 5000;
+
+ /**
+ * Number of times we attempt to restart supplicant
+ */
+ private static final int SUPPLICANT_RESTART_TRIES = 5;
+
+ private int mSupplicantRestartCount = 0;
+ /* Tracks sequence number on stop failure message */
+ private int mSupplicantStopFailureToken = 0;
+
+ /**
+ * Tether state change notification time out
+ */
+ private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000;
+
+ /* Tracks sequence number on a tether notification time out */
+ private int mTetherToken = 0;
+
+ /**
+ * Driver start time out.
+ */
+ private static final int DRIVER_START_TIME_OUT_MSECS = 10000;
+
+ /* Tracks sequence number on a driver time out */
+ private int mDriverStartToken = 0;
+
+ /**
+ * The link properties of the wifi interface.
+ * Do not modify this directly; use updateLinkProperties instead.
+ */
+ private LinkProperties mLinkProperties;
+
+ /**
+ * Subset of link properties coming from netlink.
+ * Currently includes IPv4 and IPv6 addresses. In the future will also include IPv6 DNS servers
+ * and domains obtained from router advertisements (RFC 6106).
+ */
+ private final LinkProperties mNetlinkLinkProperties;
+
+ /* Tracks sequence number on a periodic scan message */
+ private int mPeriodicScanToken = 0;
+
+ // Wakelock held during wifi start/stop and driver load/unload
+ private PowerManager.WakeLock mWakeLock;
+
+ private Context mContext;
+
+ private final Object mDhcpResultsLock = new Object();
+ private DhcpResults mDhcpResults;
+ private WifiInfo mWifiInfo;
+ private NetworkInfo mNetworkInfo;
+ private SupplicantStateTracker mSupplicantStateTracker;
+ private DhcpStateMachine mDhcpStateMachine;
+ private boolean mDhcpActive = false;
+
+ private class InterfaceObserver extends BaseNetworkObserver {
+ private WifiStateMachine mWifiStateMachine;
+
+ InterfaceObserver(WifiStateMachine wifiStateMachine) {
+ super();
+ mWifiStateMachine = wifiStateMachine;
+ }
+
+ private void maybeLog(String operation, String iface, LinkAddress address) {
+ if (DBG) {
+ log(operation + ": " + address + " on " + iface +
+ " flags " + address.getFlags() + " scope " + address.getScope());
+ }
+ }
+
+ @Override
+ public void addressUpdated(String iface, LinkAddress address) {
+ if (mWifiStateMachine.mInterfaceName.equals(iface)) {
+ maybeLog("addressUpdated", iface, address);
+ mWifiStateMachine.sendMessage(CMD_IP_ADDRESS_UPDATED, address);
+ }
+ }
+
+ @Override
+ public void addressRemoved(String iface, LinkAddress address) {
+ if (mWifiStateMachine.mInterfaceName.equals(iface)) {
+ maybeLog("addressRemoved", iface, address);
+ mWifiStateMachine.sendMessage(CMD_IP_ADDRESS_REMOVED, address);
+ }
+ }
+ }
+
+ private InterfaceObserver mInterfaceObserver;
+
+ private AlarmManager mAlarmManager;
+ private PendingIntent mScanIntent;
+ private PendingIntent mDriverStopIntent;
+ private PendingIntent mBatchedScanIntervalIntent;
+
+ /* Tracks current frequency mode */
+ private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
+
+ /* Tracks if we are filtering Multicast v4 packets. Default is to filter. */
+ private AtomicBoolean mFilteringMulticastV4Packets = new AtomicBoolean(true);
+
+ // Channel for sending replies.
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+
+ private WifiP2pManager mWifiP2pManager;
+ //Used to initiate a connection with WifiP2pService
+ private AsyncChannel mWifiP2pChannel;
+ private AsyncChannel mWifiApConfigChannel;
+
+ /* The base for wifi message types */
+ static final int BASE = Protocol.BASE_WIFI;
+ /* Start the supplicant */
+ static final int CMD_START_SUPPLICANT = BASE + 11;
+ /* Stop the supplicant */
+ static final int CMD_STOP_SUPPLICANT = BASE + 12;
+ /* Start the driver */
+ static final int CMD_START_DRIVER = BASE + 13;
+ /* Stop the driver */
+ static final int CMD_STOP_DRIVER = BASE + 14;
+ /* Indicates Static IP succeeded */
+ static final int CMD_STATIC_IP_SUCCESS = BASE + 15;
+ /* Indicates Static IP failed */
+ static final int CMD_STATIC_IP_FAILURE = BASE + 16;
+ /* Indicates supplicant stop failed */
+ static final int CMD_STOP_SUPPLICANT_FAILED = BASE + 17;
+ /* Delayed stop to avoid shutting down driver too quick*/
+ static final int CMD_DELAYED_STOP_DRIVER = BASE + 18;
+ /* A delayed message sent to start driver when it fail to come up */
+ static final int CMD_DRIVER_START_TIMED_OUT = BASE + 19;
+
+ /* Start the soft access point */
+ static final int CMD_START_AP = BASE + 21;
+ /* Indicates soft ap start succeeded */
+ static final int CMD_START_AP_SUCCESS = BASE + 22;
+ /* Indicates soft ap start failed */
+ static final int CMD_START_AP_FAILURE = BASE + 23;
+ /* Stop the soft access point */
+ static final int CMD_STOP_AP = BASE + 24;
+ /* Set the soft access point configuration */
+ static final int CMD_SET_AP_CONFIG = BASE + 25;
+ /* Soft access point configuration set completed */
+ static final int CMD_SET_AP_CONFIG_COMPLETED = BASE + 26;
+ /* Request the soft access point configuration */
+ static final int CMD_REQUEST_AP_CONFIG = BASE + 27;
+ /* Response to access point configuration request */
+ static final int CMD_RESPONSE_AP_CONFIG = BASE + 28;
+ /* Invoked when getting a tether state change notification */
+ static final int CMD_TETHER_STATE_CHANGE = BASE + 29;
+ /* A delayed message sent to indicate tether state change failed to arrive */
+ static final int CMD_TETHER_NOTIFICATION_TIMED_OUT = BASE + 30;
+
+ static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE = BASE + 31;
+
+ /* Supplicant commands */
+ /* Is supplicant alive ? */
+ static final int CMD_PING_SUPPLICANT = BASE + 51;
+ /* Add/update a network configuration */
+ static final int CMD_ADD_OR_UPDATE_NETWORK = BASE + 52;
+ /* Delete a network */
+ static final int CMD_REMOVE_NETWORK = BASE + 53;
+ /* Enable a network. The device will attempt a connection to the given network. */
+ static final int CMD_ENABLE_NETWORK = BASE + 54;
+ /* Enable all networks */
+ static final int CMD_ENABLE_ALL_NETWORKS = BASE + 55;
+ /* Blacklist network. De-prioritizes the given BSSID for connection. */
+ static final int CMD_BLACKLIST_NETWORK = BASE + 56;
+ /* Clear the blacklist network list */
+ static final int CMD_CLEAR_BLACKLIST = BASE + 57;
+ /* Save configuration */
+ static final int CMD_SAVE_CONFIG = BASE + 58;
+ /* Get configured networks*/
+ static final int CMD_GET_CONFIGURED_NETWORKS = BASE + 59;
+
+ /* Supplicant commands after driver start*/
+ /* Initiate a scan */
+ static final int CMD_START_SCAN = BASE + 71;
+ /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
+ static final int CMD_SET_OPERATIONAL_MODE = BASE + 72;
+ /* Disconnect from a network */
+ static final int CMD_DISCONNECT = BASE + 73;
+ /* Reconnect to a network */
+ static final int CMD_RECONNECT = BASE + 74;
+ /* Reassociate to a network */
+ static final int CMD_REASSOCIATE = BASE + 75;
+ /* Controls suspend mode optimizations
+ *
+ * When high perf mode is enabled, suspend mode optimizations are disabled
+ *
+ * When high perf mode is disabled, suspend mode optimizations are enabled
+ *
+ * Suspend mode optimizations include:
+ * - packet filtering
+ * - turn off roaming
+ * - DTIM wake up settings
+ */
+ static final int CMD_SET_HIGH_PERF_MODE = BASE + 77;
+ /* Set the country code */
+ static final int CMD_SET_COUNTRY_CODE = BASE + 80;
+ /* Enables RSSI poll */
+ static final int CMD_ENABLE_RSSI_POLL = BASE + 82;
+ /* RSSI poll */
+ static final int CMD_RSSI_POLL = BASE + 83;
+ /* Set up packet filtering */
+ static final int CMD_START_PACKET_FILTERING = BASE + 84;
+ /* Clear packet filter */
+ static final int CMD_STOP_PACKET_FILTERING = BASE + 85;
+ /* Enable suspend mode optimizations in the driver */
+ static final int CMD_SET_SUSPEND_OPT_ENABLED = BASE + 86;
+ /* When there are no saved networks, we do a periodic scan to notify user of
+ * an open network */
+ static final int CMD_NO_NETWORKS_PERIODIC_SCAN = BASE + 88;
+
+ /* arg1 values to CMD_STOP_PACKET_FILTERING and CMD_START_PACKET_FILTERING */
+ static final int MULTICAST_V6 = 1;
+ static final int MULTICAST_V4 = 0;
+
+ /* Set the frequency band */
+ static final int CMD_SET_FREQUENCY_BAND = BASE + 90;
+ /* Enable background scan for configured networks */
+ static final int CMD_ENABLE_BACKGROUND_SCAN = BASE + 91;
+ /* Enable TDLS on a specific MAC address */
+ static final int CMD_ENABLE_TDLS = BASE + 92;
+
+ /* Commands from/to the SupplicantStateTracker */
+ /* Reset the supplicant state tracker */
+ static final int CMD_RESET_SUPPLICANT_STATE = BASE + 111;
+
+ /* P2p commands */
+ /* We are ok with no response here since we wont do much with it anyway */
+ public static final int CMD_ENABLE_P2P = BASE + 131;
+ /* In order to shut down supplicant cleanly, we wait till p2p has
+ * been disabled */
+ public static final int CMD_DISABLE_P2P_REQ = BASE + 132;
+ public static final int CMD_DISABLE_P2P_RSP = BASE + 133;
+
+ public static final int CMD_BOOT_COMPLETED = BASE + 134;
+
+ /* change the batch scan settings.
+ * arg1 = responsible UID
+ * arg2 = csph (channel scans per hour)
+ * obj = bundle with the new settings and the optional worksource
+ */
+ public static final int CMD_SET_BATCHED_SCAN = BASE + 135;
+ public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136;
+ public static final int CMD_POLL_BATCHED_SCAN = BASE + 137;
+
+ /* Link configuration (IP address, DNS, ...) changes */
+ /* An new IP address was added to our interface, or an existing IP address was updated */
+ static final int CMD_IP_ADDRESS_UPDATED = BASE + 140;
+ /* An IP address was removed from our interface */
+ static final int CMD_IP_ADDRESS_REMOVED = BASE + 141;
+ /* Reload all networks and reconnect */
+ static final int CMD_RELOAD_TLS_AND_RECONNECT = BASE + 142;
+
+ /* Wifi state machine modes of operation */
+ /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
+ public static final int CONNECT_MODE = 1;
+ /* SCAN_ONLY_MODE - don't connect to any APs; scan, but only while apps hold lock */
+ public static final int SCAN_ONLY_MODE = 2;
+ /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
+ public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
+
+ private static final int SUCCESS = 1;
+ private static final int FAILURE = -1;
+
+ /**
+ * The maximum number of times we will retry a connection to an access point
+ * for which we have failed in acquiring an IP address from DHCP. A value of
+ * N means that we will make N+1 connection attempts in all.
+ * <p>
+ * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
+ * value if a Settings value is not present.
+ */
+ private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
+
+ /* Tracks if suspend optimizations need to be disabled by DHCP,
+ * screen or due to high perf mode.
+ * When any of them needs to disable it, we keep the suspend optimizations
+ * disabled
+ */
+ private int mSuspendOptNeedsDisabled = 0;
+
+ private static final int SUSPEND_DUE_TO_DHCP = 1;
+ private static final int SUSPEND_DUE_TO_HIGH_PERF = 1<<1;
+ private static final int SUSPEND_DUE_TO_SCREEN = 1<<2;
+
+ /* Tracks if user has enabled suspend optimizations through settings */
+ private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
+
+ /**
+ * Default framework scan interval in milliseconds. This is used in the scenario in which
+ * wifi chipset does not support background scanning to set up a
+ * periodic wake up scan so that the device can connect to a new access
+ * point on the move. {@link Settings.Global#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
+ * override this.
+ */
+ private final int mDefaultFrameworkScanIntervalMs;
+
+ /**
+ * Supplicant scan interval in milliseconds.
+ * Comes from {@link Settings.Global#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
+ * from the default config if the setting is not set
+ */
+ private long mSupplicantScanIntervalMs;
+
+ /**
+ * Minimum time interval between enabling all networks.
+ * A device can end up repeatedly connecting to a bad network on screen on/off toggle
+ * due to enabling every time. We add a threshold to avoid this.
+ */
+ private static final int MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS = 10 * 60 * 1000; /* 10 minutes */
+ private long mLastEnableAllNetworksTime;
+
+ /**
+ * Starting and shutting down driver too quick causes problems leading to driver
+ * being in a bad state. Delay driver stop.
+ */
+ private final int mDriverStopDelayMs;
+ private int mDelayedStopCounter;
+ private boolean mInDelayedStop = false;
+
+ // sometimes telephony gives us this data before boot is complete and we can't store it
+ // until after, so the write is deferred
+ private volatile String mPersistedCountryCode;
+
+ // Supplicant doesn't like setting the same country code multiple times (it may drop
+ // currently connected network), so we save the country code here to avoid redundency
+ private String mLastSetCountryCode;
+
+ private static final int MIN_RSSI = -200;
+ private static final int MAX_RSSI = 256;
+
+ /* Default parent state */
+ private State mDefaultState = new DefaultState();
+ /* Temporary initial state */
+ private State mInitialState = new InitialState();
+ /* Driver loaded, waiting for supplicant to start */
+ private State mSupplicantStartingState = new SupplicantStartingState();
+ /* Driver loaded and supplicant ready */
+ private State mSupplicantStartedState = new SupplicantStartedState();
+ /* Waiting for supplicant to stop and monitor to exit */
+ private State mSupplicantStoppingState = new SupplicantStoppingState();
+ /* Driver start issued, waiting for completed event */
+ private State mDriverStartingState = new DriverStartingState();
+ /* Driver started */
+ private State mDriverStartedState = new DriverStartedState();
+ /* Wait until p2p is disabled
+ * This is a special state which is entered right after we exit out of DriverStartedState
+ * before transitioning to another state.
+ */
+ private State mWaitForP2pDisableState = new WaitForP2pDisableState();
+ /* Driver stopping */
+ private State mDriverStoppingState = new DriverStoppingState();
+ /* Driver stopped */
+ private State mDriverStoppedState = new DriverStoppedState();
+ /* Scan for networks, no connection will be established */
+ private State mScanModeState = new ScanModeState();
+ /* Connecting to an access point */
+ private State mConnectModeState = new ConnectModeState();
+ /* Connected at 802.11 (L2) level */
+ private State mL2ConnectedState = new L2ConnectedState();
+ /* fetching IP after connection to access point (assoc+auth complete) */
+ private State mObtainingIpState = new ObtainingIpState();
+ /* Waiting for link quality verification to be complete */
+ private State mVerifyingLinkState = new VerifyingLinkState();
+ /* Connected with IP addr */
+ private State mConnectedState = new ConnectedState();
+ /* disconnect issued, waiting for network disconnect confirmation */
+ private State mDisconnectingState = new DisconnectingState();
+ /* Network is not connected, supplicant assoc+auth is not complete */
+ private State mDisconnectedState = new DisconnectedState();
+ /* Waiting for WPS to be completed*/
+ private State mWpsRunningState = new WpsRunningState();
+
+ /* Soft ap is starting up */
+ private State mSoftApStartingState = new SoftApStartingState();
+ /* Soft ap is running */
+ private State mSoftApStartedState = new SoftApStartedState();
+ /* Soft ap is running and we are waiting for tether notification */
+ private State mTetheringState = new TetheringState();
+ /* Soft ap is running and we are tethered through connectivity service */
+ private State mTetheredState = new TetheredState();
+ /* Waiting for untether confirmation before stopping soft Ap */
+ private State mUntetheringState = new UntetheringState();
+
+ private class TetherStateChange {
+ ArrayList<String> available;
+ ArrayList<String> active;
+ TetherStateChange(ArrayList<String> av, ArrayList<String> ac) {
+ available = av;
+ active = ac;
+ }
+ }
+
+
+ /**
+ * One of {@link WifiManager#WIFI_STATE_DISABLED},
+ * {@link WifiManager#WIFI_STATE_DISABLING},
+ * {@link WifiManager#WIFI_STATE_ENABLED},
+ * {@link WifiManager#WIFI_STATE_ENABLING},
+ * {@link WifiManager#WIFI_STATE_UNKNOWN}
+ *
+ */
+ private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
+
+ /**
+ * One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+ * {@link WifiManager#WIFI_AP_STATE_DISABLING},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLED},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLING},
+ * {@link WifiManager#WIFI_AP_STATE_FAILED}
+ *
+ */
+ private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
+
+ private static final int SCAN_REQUEST = 0;
+ private static final String ACTION_START_SCAN =
+ "com.android.server.WifiManager.action.START_SCAN";
+
+ private static final String DELAYED_STOP_COUNTER = "DelayedStopCounter";
+ private static final int DRIVER_STOP_REQUEST = 0;
+ private static final String ACTION_DELAYED_DRIVER_STOP =
+ "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
+
+ private static final String ACTION_REFRESH_BATCHED_SCAN =
+ "com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
+ /**
+ * Keep track of whether WIFI is running.
+ */
+ private boolean mIsRunning = false;
+
+ /**
+ * Keep track of whether we last told the battery stats we had started.
+ */
+ private boolean mReportedRunning = false;
+
+ /**
+ * Most recently set source of starting WIFI.
+ */
+ private final WorkSource mRunningWifiUids = new WorkSource();
+
+ /**
+ * The last reported UIDs that were responsible for starting WIFI.
+ */
+ private final WorkSource mLastRunningWifiUids = new WorkSource();
+
+ private final IBatteryStats mBatteryStats;
+
+ private BatchedScanSettings mBatchedScanSettings = null;
+
+ /**
+ * Track the worksource/cost of the current settings and track what's been noted
+ * to the battery stats, so we can mark the end of the previous when changing.
+ */
+ private WorkSource mBatchedScanWorkSource = null;
+ private int mBatchedScanCsph = 0;
+ private WorkSource mNotedBatchedScanWorkSource = null;
+ private int mNotedBatchedScanCsph = 0;
+
+
+ public WifiStateMachine(Context context, String wlanInterface) {
+ super("WifiStateMachine");
+ mContext = context;
+ mInterfaceName = wlanInterface;
+
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNwService = INetworkManagementService.Stub.asInterface(b);
+
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
+ mWifiNative = new WifiNative(mInterfaceName);
+ mWifiConfigStore = new WifiConfigStore(context, mWifiNative);
+ mWifiMonitor = new WifiMonitor(this, mWifiNative);
+ mWifiInfo = new WifiInfo();
+ mSupplicantStateTracker = new SupplicantStateTracker(context, this, mWifiConfigStore,
+ getHandler());
+ mLinkProperties = new LinkProperties();
+ mNetlinkLinkProperties = new LinkProperties();
+
+ mWifiP2pManager = (WifiP2pManager) mContext.getSystemService(Context.WIFI_P2P_SERVICE);
+
+ mNetworkInfo.setIsAvailable(false);
+ mLastBssid = null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ mLastSignalLevel = -1;
+
+ mInterfaceObserver = new InterfaceObserver(this);
+ try {
+ mNwService.registerObserver(mInterfaceObserver);
+ } catch (RemoteException e) {
+ loge("Couldn't register interface observer: " + e.toString());
+ }
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent scanIntent = new Intent(ACTION_START_SCAN, null);
+ mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
+
+ Intent batchedIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+ mBatchedScanIntervalIntent = PendingIntent.getBroadcast(mContext, 0, batchedIntent, 0);
+
+ mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
+ R.integer.config_wifi_framework_scan_interval);
+
+ mDriverStopDelayMs = mContext.getResources().getInteger(
+ R.integer.config_wifi_driver_stop_delay);
+
+ mBackgroundScanSupported = mContext.getResources().getBoolean(
+ R.bool.config_wifi_background_scan_support);
+
+ mPrimaryDeviceType = mContext.getResources().getString(
+ R.string.config_wifi_p2p_device_type);
+
+ mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ArrayList<String> available = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ ArrayList<String> active = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_ACTIVE_TETHER);
+ sendMessage(CMD_TETHER_STATE_CHANGE, new TetherStateChange(available, active));
+ }
+ },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final WorkSource workSource = null;
+ startScan(UNKNOWN_SCAN_SOURCE, workSource);
+ }
+ },
+ new IntentFilter(ACTION_START_SCAN));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ handleScreenStateChanged(true);
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ handleScreenStateChanged(false);
+ } else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
+ startNextBatchedScanAsync();
+ }
+ }
+ }, filter);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
+ sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
+ }
+ },
+ new IntentFilter(ACTION_DELAYED_DRIVER_STOP));
+
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false,
+ new ContentObserver(getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+ }
+ });
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sendMessage(CMD_BOOT_COMPLETED);
+ }
+ },
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+
+ mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE);
+
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getName());
+
+ mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend");
+ mSuspendWakeLock.setReferenceCounted(false);
+
+ addState(mDefaultState);
+ addState(mInitialState, mDefaultState);
+ addState(mSupplicantStartingState, mDefaultState);
+ addState(mSupplicantStartedState, mDefaultState);
+ addState(mDriverStartingState, mSupplicantStartedState);
+ addState(mDriverStartedState, mSupplicantStartedState);
+ addState(mScanModeState, mDriverStartedState);
+ addState(mConnectModeState, mDriverStartedState);
+ addState(mL2ConnectedState, mConnectModeState);
+ addState(mObtainingIpState, mL2ConnectedState);
+ addState(mVerifyingLinkState, mL2ConnectedState);
+ addState(mConnectedState, mL2ConnectedState);
+ addState(mDisconnectingState, mConnectModeState);
+ addState(mDisconnectedState, mConnectModeState);
+ addState(mWpsRunningState, mConnectModeState);
+ addState(mWaitForP2pDisableState, mSupplicantStartedState);
+ addState(mDriverStoppingState, mSupplicantStartedState);
+ addState(mDriverStoppedState, mSupplicantStartedState);
+ addState(mSupplicantStoppingState, mDefaultState);
+ addState(mSoftApStartingState, mDefaultState);
+ addState(mSoftApStartedState, mDefaultState);
+ addState(mTetheringState, mSoftApStartedState);
+ addState(mTetheredState, mSoftApStartedState);
+ addState(mUntetheringState, mSoftApStartedState);
+
+ setInitialState(mInitialState);
+
+ setLogRecSize(2000);
+ setLogOnlyTransitions(false);
+ if (DBG) setDbg(true);
+
+ //start the state machine
+ start();
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /*********************************************************
+ * Methods exposed for public use
+ ********************************************************/
+
+ public Messenger getMessenger() {
+ return new Messenger(getHandler());
+ }
+ /**
+ * TODO: doc
+ */
+ public boolean syncPingSupplicant(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_PING_SUPPLICANT);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Initiate a wifi scan. If workSource is not null, blame is given to it,
+ * otherwise blame is given to callingUid.
+ *
+ * @param callingUid The uid initiating the wifi scan. Blame will be given
+ * here unless workSource is specified.
+ * @param workSource If not null, blame is given to workSource.
+ */
+ public void startScan(int callingUid, WorkSource workSource) {
+ sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
+ }
+
+ /**
+ * start or stop batched scanning using the given settings
+ */
+ private static final String BATCHED_SETTING = "batched_settings";
+ private static final String BATCHED_WORKSOURCE = "batched_worksource";
+ public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph,
+ WorkSource workSource) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BATCHED_SETTING, settings);
+ bundle.putParcelable(BATCHED_WORKSOURCE, workSource);
+ sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle);
+ }
+
+ public List<BatchedScanResult> syncGetBatchedScanResultsList() {
+ synchronized (mBatchedScanResults) {
+ List<BatchedScanResult> batchedScanList =
+ new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
+ for(BatchedScanResult result: mBatchedScanResults) {
+ batchedScanList.add(new BatchedScanResult(result));
+ }
+ return batchedScanList;
+ }
+ }
+
+ public void requestBatchedScanPoll() {
+ sendMessage(CMD_POLL_BATCHED_SCAN);
+ }
+
+ private void startBatchedScan() {
+ if (mBatchedScanSettings == null) return;
+
+ if (mDhcpActive) {
+ if (DBG) log("not starting Batched Scans due to DHCP");
+ return;
+ }
+
+ // first grab any existing data
+ retrieveBatchedScanData();
+
+ mAlarmManager.cancel(mBatchedScanIntervalIntent);
+
+ String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
+ try {
+ mExpectedBatchedScans = Integer.parseInt(scansExpected);
+ setNextBatchedAlarm(mExpectedBatchedScans);
+ if (mExpectedBatchedScans > 0) noteBatchedScanStart();
+ } catch (NumberFormatException e) {
+ stopBatchedScan();
+ loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
+ }
+ }
+
+ // called from BroadcastListener
+ private void startNextBatchedScanAsync() {
+ sendMessage(CMD_START_NEXT_BATCHED_SCAN);
+ }
+
+ private void startNextBatchedScan() {
+ // first grab any existing data
+ retrieveBatchedScanData();
+
+ setNextBatchedAlarm(mExpectedBatchedScans);
+ }
+
+ private void handleBatchedScanPollRequest() {
+ if (DBG) {
+ log("handleBatchedScanPoll Request - mBatchedScanMinPollTime=" +
+ mBatchedScanMinPollTime + " , mBatchedScanSettings=" +
+ mBatchedScanSettings);
+ }
+ // if there is no appropriate PollTime that's because we either aren't
+ // batching or we've already set a time for a poll request
+ if (mBatchedScanMinPollTime == 0) return;
+ if (mBatchedScanSettings == null) return;
+
+ long now = System.currentTimeMillis();
+
+ if (now > mBatchedScanMinPollTime) {
+ // do the poll and reset our timers
+ startNextBatchedScan();
+ } else {
+ mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, mBatchedScanMinPollTime,
+ mBatchedScanIntervalIntent);
+ mBatchedScanMinPollTime = 0;
+ }
+ }
+
+ // return true if new/different
+ private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) {
+ BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING);
+ WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE);
+
+ if (DBG) {
+ log("set batched scan to " + settings + " for uid=" + responsibleUid +
+ ", worksource=" + responsibleWorkSource);
+ }
+ if (settings != null) {
+ if (settings.equals(mBatchedScanSettings)) return false;
+ } else {
+ if (mBatchedScanSettings == null) return false;
+ }
+ mBatchedScanSettings = settings;
+ if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid);
+ mBatchedScanWorkSource = responsibleWorkSource;
+ mBatchedScanCsph = csph;
+ return true;
+ }
+
+ private void stopBatchedScan() {
+ mAlarmManager.cancel(mBatchedScanIntervalIntent);
+ retrieveBatchedScanData();
+ mWifiNative.setBatchedScanSettings(null);
+ noteBatchedScanStop();
+ }
+
+ private void setNextBatchedAlarm(int scansExpected) {
+
+ if (mBatchedScanSettings == null || scansExpected < 1) return;
+
+ mBatchedScanMinPollTime = System.currentTimeMillis() +
+ mBatchedScanSettings.scanIntervalSec * 1000;
+
+ if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
+ scansExpected = mBatchedScanSettings.maxScansPerBatch;
+ }
+
+ int secToFull = mBatchedScanSettings.scanIntervalSec;
+ secToFull *= scansExpected;
+
+ int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
+ if (debugPeriod > 0) secToFull = debugPeriod;
+
+ // set the alarm to do the next poll. We set it a little short as we'd rather
+ // wake up wearly than miss a scan due to buffer overflow
+ mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ + ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
+ mBatchedScanIntervalIntent);
+ }
+
+ /**
+ * Start reading new scan data
+ * Data comes in as:
+ * "scancount=5\n"
+ * "nextcount=5\n"
+ * "apcount=3\n"
+ * "trunc\n" (optional)
+ * "bssid=...\n"
+ * "ssid=...\n"
+ * "freq=...\n" (in Mhz)
+ * "level=...\n"
+ * "dist=...\n" (in cm)
+ * "distsd=...\n" (standard deviation, in cm)
+ * "===="
+ * "bssid=...\n"
+ * etc
+ * "===="
+ * "bssid=...\n"
+ * etc
+ * "%%%%"
+ * "apcount=2\n"
+ * "bssid=...\n"
+ * etc
+ * "%%%%
+ * etc
+ * "----"
+ */
+ private final static boolean DEBUG_PARSE = false;
+ private void retrieveBatchedScanData() {
+ String rawData = mWifiNative.getBatchedScanResults();
+ if (DEBUG_PARSE) log("rawData = " + rawData);
+ mBatchedScanMinPollTime = 0;
+ if (rawData == null || rawData.equalsIgnoreCase("OK")) {
+ loge("Unexpected BatchedScanResults :" + rawData);
+ return;
+ }
+
+ int scanCount = 0;
+ final String END_OF_BATCHES = "----";
+ final String SCANCOUNT = "scancount=";
+ final String TRUNCATED = "trunc";
+ final String AGE = "age=";
+ final String DIST = "dist=";
+ final String DISTSD = "distSd=";
+
+ String splitData[] = rawData.split("\n");
+ int n = 0;
+ if (splitData[n].startsWith(SCANCOUNT)) {
+ try {
+ scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
+ } catch (NumberFormatException e) {
+ loge("scancount parseInt Exception from " + splitData[n]);
+ }
+ } else log("scancount not found");
+ if (scanCount == 0) {
+ loge("scanCount==0 - aborting");
+ return;
+ }
+
+ final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ synchronized (mBatchedScanResults) {
+ mBatchedScanResults.clear();
+ BatchedScanResult batchedScanResult = new BatchedScanResult();
+
+ String bssid = null;
+ WifiSsid wifiSsid = null;
+ int level = 0;
+ int freq = 0;
+ int dist, distSd;
+ long tsf = 0;
+ dist = distSd = ScanResult.UNSPECIFIED;
+ final long now = SystemClock.elapsedRealtime();
+ final int bssidStrLen = BSSID_STR.length();
+
+ while (true) {
+ while (n < splitData.length) {
+ if (DEBUG_PARSE) logd("parsing " + splitData[n]);
+ if (splitData[n].equals(END_OF_BATCHES)) {
+ if (n+1 != splitData.length) {
+ loge("didn't consume " + (splitData.length-n));
+ }
+ if (mBatchedScanResults.size() > 0) {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ logd("retrieveBatchedScanResults X");
+ return;
+ }
+ if ((splitData[n].equals(END_STR)) || splitData[n].equals(DELIMITER_STR)) {
+ if (bssid != null) {
+ batchedScanResult.scanResults.add(new ScanResult(
+ wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
+ wifiSsid = null;
+ bssid = null;
+ level = 0;
+ freq = 0;
+ tsf = 0;
+ dist = distSd = ScanResult.UNSPECIFIED;
+ }
+ if (splitData[n].equals(END_STR)) {
+ if (batchedScanResult.scanResults.size() != 0) {
+ mBatchedScanResults.add(batchedScanResult);
+ batchedScanResult = new BatchedScanResult();
+ } else {
+ logd("Found empty batch");
+ }
+ }
+ } else if (splitData[n].equals(TRUNCATED)) {
+ batchedScanResult.truncated = true;
+ } else if (splitData[n].startsWith(BSSID_STR)) {
+ bssid = new String(splitData[n].getBytes(), bssidStrLen,
+ splitData[n].length() - bssidStrLen);
+ } else if (splitData[n].startsWith(FREQ_STR)) {
+ try {
+ freq = Integer.parseInt(splitData[n].substring(FREQ_STR.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid freqency: " + splitData[n]);
+ freq = 0;
+ }
+ } else if (splitData[n].startsWith(AGE)) {
+ try {
+ tsf = now - Long.parseLong(splitData[n].substring(AGE.length()));
+ tsf *= 1000; // convert mS -> uS
+ } catch (NumberFormatException e) {
+ loge("Invalid timestamp: " + splitData[n]);
+ tsf = 0;
+ }
+ } else if (splitData[n].startsWith(SSID_STR)) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ splitData[n].substring(SSID_STR.length()));
+ } else if (splitData[n].startsWith(LEVEL_STR)) {
+ try {
+ level = Integer.parseInt(splitData[n].substring(LEVEL_STR.length()));
+ if (level > 0) level -= 256;
+ } catch (NumberFormatException e) {
+ loge("Invalid level: " + splitData[n]);
+ level = 0;
+ }
+ } else if (splitData[n].startsWith(DIST)) {
+ try {
+ dist = Integer.parseInt(splitData[n].substring(DIST.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid distance: " + splitData[n]);
+ dist = ScanResult.UNSPECIFIED;
+ }
+ } else if (splitData[n].startsWith(DISTSD)) {
+ try {
+ distSd = Integer.parseInt(splitData[n].substring(DISTSD.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid distanceSd: " + splitData[n]);
+ distSd = ScanResult.UNSPECIFIED;
+ }
+ } else {
+ loge("Unable to parse batched scan result line: " + splitData[n]);
+ }
+ n++;
+ }
+ rawData = mWifiNative.getBatchedScanResults();
+ if (DEBUG_PARSE) log("reading more data:\n" + rawData);
+ if (rawData == null) {
+ loge("Unexpected null BatchedScanResults");
+ return;
+ }
+ splitData = rawData.split("\n");
+ if (splitData.length == 0 || splitData[0].equals("ok")) {
+ loge("batch scan results just ended!");
+ if (mBatchedScanResults.size() > 0) {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ return;
+ }
+ n = 0;
+ }
+ }
+ }
+
+ // If workSource is not null, blame is given to it, otherwise blame is given to callingUid.
+ private void noteScanStart(int callingUid, WorkSource workSource) {
+ if (mScanWorkSource == null && (callingUid != UNKNOWN_SCAN_SOURCE || workSource != null)) {
+ mScanWorkSource = workSource != null ? workSource : new WorkSource(callingUid);
+ try {
+ mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ }
+ }
+ }
+
+ private void noteScanEnd() {
+ if (mScanWorkSource != null) {
+ try {
+ mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mScanWorkSource = null;
+ }
+ }
+ }
+
+ private void noteBatchedScanStart() {
+ // note the end of a previous scan set
+ if (mNotedBatchedScanWorkSource != null &&
+ (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false ||
+ mNotedBatchedScanCsph != mBatchedScanCsph)) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mNotedBatchedScanWorkSource = null;
+ mNotedBatchedScanCsph = 0;
+ }
+ }
+ // note the start of the new
+ try {
+ mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource,
+ mBatchedScanCsph);
+ mNotedBatchedScanWorkSource = mBatchedScanWorkSource;
+ mNotedBatchedScanCsph = mBatchedScanCsph;
+ } catch (RemoteException e) {
+ log(e.toString());
+ }
+ }
+
+ private void noteBatchedScanStop() {
+ if (mNotedBatchedScanWorkSource != null) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mNotedBatchedScanWorkSource = null;
+ mNotedBatchedScanCsph = 0;
+ }
+ }
+ }
+
+ private void startScanNative(int type) {
+ mWifiNative.scan(type);
+ mScanResultIsPending = true;
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setSupplicantRunning(boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_SUPPLICANT);
+ } else {
+ sendMessage(CMD_STOP_SUPPLICANT);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_AP, wifiConfig);
+ } else {
+ sendMessage(CMD_STOP_AP);
+ }
+ }
+
+ public void setWifiApConfiguration(WifiConfiguration config) {
+ mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
+ }
+
+ public WifiConfiguration syncGetWifiApConfiguration() {
+ Message resultMsg = mWifiApConfigChannel.sendMessageSynchronously(CMD_REQUEST_AP_CONFIG);
+ WifiConfiguration ret = (WifiConfiguration) resultMsg.obj;
+ resultMsg.recycle();
+ return ret;
+ }
+
+ /**
+ * TODO: doc
+ */
+ public int syncGetWifiState() {
+ return mWifiState.get();
+ }
+
+ /**
+ * TODO: doc
+ */
+ public String syncGetWifiStateByName() {
+ switch (mWifiState.get()) {
+ case WIFI_STATE_DISABLING:
+ return "disabling";
+ case WIFI_STATE_DISABLED:
+ return "disabled";
+ case WIFI_STATE_ENABLING:
+ return "enabling";
+ case WIFI_STATE_ENABLED:
+ return "enabled";
+ case WIFI_STATE_UNKNOWN:
+ return "unknown state";
+ default:
+ return "[invalid state]";
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public int syncGetWifiApState() {
+ return mWifiApState.get();
+ }
+
+ /**
+ * TODO: doc
+ */
+ public String syncGetWifiApStateByName() {
+ switch (mWifiApState.get()) {
+ case WIFI_AP_STATE_DISABLING:
+ return "disabling";
+ case WIFI_AP_STATE_DISABLED:
+ return "disabled";
+ case WIFI_AP_STATE_ENABLING:
+ return "enabling";
+ case WIFI_AP_STATE_ENABLED:
+ return "enabled";
+ case WIFI_AP_STATE_FAILED:
+ return "failed";
+ default:
+ return "[invalid state]";
+ }
+ }
+
+ /**
+ * Get status information for the current connection, if any.
+ * @return a {@link WifiInfo} object containing information about the current connection
+ *
+ */
+ public WifiInfo syncRequestConnectionInfo() {
+ return mWifiInfo;
+ }
+
+ public DhcpResults syncGetDhcpResults() {
+ synchronized (mDhcpResultsLock) {
+ return new DhcpResults(mDhcpResults);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setDriverStart(boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_DRIVER);
+ } else {
+ sendMessage(CMD_STOP_DRIVER);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setOperationalMode(int mode) {
+ if (DBG) log("setting operational mode to " + String.valueOf(mode));
+ sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
+ }
+
+ /**
+ * TODO: doc
+ */
+ public List<ScanResult> syncGetScanResultsList() {
+ synchronized (mScanResultCache) {
+ List<ScanResult> scanList = new ArrayList<ScanResult>();
+ for(ScanResult result: mScanResults) {
+ scanList.add(new ScanResult(result));
+ }
+ return scanList;
+ }
+ }
+
+ /**
+ * Disconnect from Access Point
+ */
+ public void disconnectCommand() {
+ sendMessage(CMD_DISCONNECT);
+ }
+
+ /**
+ * Initiate a reconnection to AP
+ */
+ public void reconnectCommand() {
+ sendMessage(CMD_RECONNECT);
+ }
+
+ /**
+ * Initiate a re-association to AP
+ */
+ public void reassociateCommand() {
+ sendMessage(CMD_REASSOCIATE);
+ }
+
+ /**
+ * Reload networks and then reconnect; helps load correct data for TLS networks
+ */
+
+ public void reloadTlsNetworksAndReconnect() {
+ sendMessage(CMD_RELOAD_TLS_AND_RECONNECT);
+ }
+
+ /**
+ * Add a network synchronously
+ *
+ * @return network id of the new network
+ */
+ public int syncAddOrUpdateNetwork(AsyncChannel channel, WifiConfiguration config) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_NETWORK, config);
+ int result = resultMsg.arg1;
+ resultMsg.recycle();
+ return result;
+ }
+
+ public List<WifiConfiguration> syncGetConfiguredNetworks(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CONFIGURED_NETWORKS);
+ List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Delete a network
+ *
+ * @param networkId id of the network to be removed
+ */
+ public boolean syncRemoveNetwork(AsyncChannel channel, int networkId) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_NETWORK, networkId);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Enable a network
+ *
+ * @param netId network id of the network
+ * @param disableOthers true, if all other networks have to be disabled
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ */
+ public boolean syncEnableNetwork(AsyncChannel channel, int netId, boolean disableOthers) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_ENABLE_NETWORK, netId,
+ disableOthers ? 1 : 0);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Disable a network
+ *
+ * @param netId network id of the network
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ */
+ public boolean syncDisableNetwork(AsyncChannel channel, int netId) {
+ Message resultMsg = channel.sendMessageSynchronously(WifiManager.DISABLE_NETWORK, netId);
+ boolean result = (resultMsg.arg1 != WifiManager.DISABLE_NETWORK_FAILED);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Blacklist a BSSID. This will avoid the AP if there are
+ * alternate APs to connect
+ *
+ * @param bssid BSSID of the network
+ */
+ public void addToBlacklist(String bssid) {
+ sendMessage(CMD_BLACKLIST_NETWORK, bssid);
+ }
+
+ /**
+ * Clear the blacklist list
+ *
+ */
+ public void clearBlacklist() {
+ sendMessage(CMD_CLEAR_BLACKLIST);
+ }
+
+ public void enableRssiPolling(boolean enabled) {
+ sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
+ }
+
+ public void enableBackgroundScanCommand(boolean enabled) {
+ sendMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0);
+ }
+
+ public void enableAllNetworks() {
+ sendMessage(CMD_ENABLE_ALL_NETWORKS);
+ }
+
+ /**
+ * Start filtering Multicast v4 packets
+ */
+ public void startFilteringMulticastV4Packets() {
+ mFilteringMulticastV4Packets.set(true);
+ sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0);
+ }
+
+ /**
+ * Stop filtering Multicast v4 packets
+ */
+ public void stopFilteringMulticastV4Packets() {
+ mFilteringMulticastV4Packets.set(false);
+ sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0);
+ }
+
+ /**
+ * Start filtering Multicast v4 packets
+ */
+ public void startFilteringMulticastV6Packets() {
+ sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0);
+ }
+
+ /**
+ * Stop filtering Multicast v4 packets
+ */
+ public void stopFilteringMulticastV6Packets() {
+ sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0);
+ }
+
+ /**
+ * Set high performance mode of operation.
+ * Enabling would set active power mode and disable suspend optimizations;
+ * disabling would set auto power mode and enable suspend optimizations
+ * @param enable true if enable, false otherwise
+ */
+ public void setHighPerfModeEnabled(boolean enable) {
+ sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
+ }
+
+ /**
+ * Set the country code
+ * @param countryCode following ISO 3166 format
+ * @param persist {@code true} if the setting should be remembered.
+ */
+ public void setCountryCode(String countryCode, boolean persist) {
+ if (persist) {
+ mPersistedCountryCode = countryCode;
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE,
+ countryCode);
+ }
+ sendMessage(CMD_SET_COUNTRY_CODE, countryCode);
+ mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.SET_COUNTRY_CODE, countryCode);
+ }
+
+ /**
+ * Set the operational frequency band
+ * @param band
+ * @param persist {@code true} if the setting should be remembered.
+ */
+ public void setFrequencyBand(int band, boolean persist) {
+ if (persist) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_FREQUENCY_BAND,
+ band);
+ }
+ sendMessage(CMD_SET_FREQUENCY_BAND, band, 0);
+ }
+
+ /**
+ * Enable TDLS for a specific MAC address
+ */
+ public void enableTdls(String remoteMacAddress, boolean enable) {
+ int enabler = enable ? 1 : 0;
+ sendMessage(CMD_ENABLE_TDLS, enabler, 0, remoteMacAddress);
+ }
+
+ /**
+ * Returns the operational frequency band
+ */
+ public int getFrequencyBand() {
+ return mFrequencyBand.get();
+ }
+
+ /**
+ * Returns the wifi configuration file
+ */
+ public String getConfigFile() {
+ return mWifiConfigStore.getConfigFile();
+ }
+
+ /**
+ * Send a message indicating bluetooth adapter connection state changed
+ */
+ public void sendBluetoothAdapterStateChange(int state) {
+ sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
+ }
+
+ /**
+ * Save configuration on supplicant
+ *
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ *
+ * TODO: deprecate this
+ */
+ public boolean syncSaveConfig(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_SAVE_CONFIG);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ public void updateBatteryWorkSource(WorkSource newSource) {
+ synchronized (mRunningWifiUids) {
+ try {
+ if (newSource != null) {
+ mRunningWifiUids.set(newSource);
+ }
+ if (mIsRunning) {
+ if (mReportedRunning) {
+ // If the work source has changed since last time, need
+ // to remove old work from battery stats.
+ if (mLastRunningWifiUids.diff(mRunningWifiUids)) {
+ mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
+ mRunningWifiUids);
+ mLastRunningWifiUids.set(mRunningWifiUids);
+ }
+ } else {
+ // Now being started, report it.
+ mBatteryStats.noteWifiRunning(mRunningWifiUids);
+ mLastRunningWifiUids.set(mRunningWifiUids);
+ mReportedRunning = true;
+ }
+ } else {
+ if (mReportedRunning) {
+ // Last reported we were running, time to stop.
+ mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
+ mLastRunningWifiUids.clear();
+ mReportedRunning = false;
+ }
+ }
+ mWakeLock.setWorkSource(newSource);
+ } catch (RemoteException ignore) {
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ mSupplicantStateTracker.dump(fd, pw, args);
+ pw.println("mLinkProperties " + mLinkProperties);
+ pw.println("mWifiInfo " + mWifiInfo);
+ pw.println("mDhcpResults " + mDhcpResults);
+ pw.println("mNetworkInfo " + mNetworkInfo);
+ pw.println("mLastSignalLevel " + mLastSignalLevel);
+ pw.println("mLastBssid " + mLastBssid);
+ pw.println("mLastNetworkId " + mLastNetworkId);
+ pw.println("mReconnectCount " + mReconnectCount);
+ pw.println("mOperationalMode " + mOperationalMode);
+ pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
+ pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+ pw.println("Supplicant status " + mWifiNative.status());
+ pw.println("mEnableBackgroundScan " + mEnableBackgroundScan);
+ pw.println();
+ mWifiConfigStore.dump(fd, pw, args);
+ }
+
+ /*********************************************************
+ * Internal private functions
+ ********************************************************/
+
+ private void handleScreenStateChanged(boolean screenOn) {
+ if (DBG) log("handleScreenStateChanged: " + screenOn);
+ enableRssiPolling(screenOn);
+ if (mBackgroundScanSupported) {
+ enableBackgroundScanCommand(screenOn == false);
+ }
+
+ if (screenOn) enableAllNetworks();
+ if (mUserWantsSuspendOpt.get()) {
+ if (screenOn) {
+ sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0);
+ } else {
+ //Allow 2s for suspend optimizations to be set
+ mSuspendWakeLock.acquire(2000);
+ sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0);
+ }
+ }
+ mScreenBroadcastReceived.set(true);
+ }
+
+ private void checkAndSetConnectivityInstance() {
+ if (mCm == null) {
+ mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+ }
+
+ private boolean startTethering(ArrayList<String> available) {
+
+ boolean wifiAvailable = false;
+
+ checkAndSetConnectivityInstance();
+
+ String[] wifiRegexs = mCm.getTetherableWifiRegexs();
+
+ for (String intf : available) {
+ for (String regex : wifiRegexs) {
+ if (intf.matches(regex)) {
+
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(intf);
+ if (ifcg != null) {
+ /* IP/netmask: 192.168.43.1/255.255.255.0 */
+ ifcg.setLinkAddress(new LinkAddress(
+ NetworkUtils.numericToInetAddress("192.168.43.1"), 24));
+ ifcg.setInterfaceUp();
+
+ mNwService.setInterfaceConfig(intf, ifcg);
+ }
+ } catch (Exception e) {
+ loge("Error configuring interface " + intf + ", :" + e);
+ return false;
+ }
+
+ if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ loge("Error tethering on " + intf);
+ return false;
+ }
+ mTetherInterfaceName = intf;
+ return true;
+ }
+ }
+ }
+ // We found no interfaces to tether
+ return false;
+ }
+
+ private void stopTethering() {
+
+ checkAndSetConnectivityInstance();
+
+ /* Clear the interface config to allow dhcp correctly configure new
+ ip settings */
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(mTetherInterfaceName);
+ if (ifcg != null) {
+ ifcg.setLinkAddress(
+ new LinkAddress(NetworkUtils.numericToInetAddress("0.0.0.0"), 0));
+ mNwService.setInterfaceConfig(mTetherInterfaceName, ifcg);
+ }
+ } catch (Exception e) {
+ loge("Error resetting interface " + mTetherInterfaceName + ", :" + e);
+ }
+
+ if (mCm.untether(mTetherInterfaceName) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ loge("Untether initiate failed!");
+ }
+ }
+
+ private boolean isWifiTethered(ArrayList<String> active) {
+
+ checkAndSetConnectivityInstance();
+
+ String[] wifiRegexs = mCm.getTetherableWifiRegexs();
+ for (String intf : active) {
+ for (String regex : wifiRegexs) {
+ if (intf.matches(regex)) {
+ return true;
+ }
+ }
+ }
+ // We found no interfaces that are tethered
+ return false;
+ }
+
+ /**
+ * Set the country code from the system setting value, if any.
+ */
+ private void setCountryCode() {
+ String countryCode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE);
+ if (countryCode != null && !countryCode.isEmpty()) {
+ setCountryCode(countryCode, false);
+ } else {
+ //use driver default
+ }
+ }
+
+ /**
+ * Set the frequency band from the system setting value, if any.
+ */
+ private void setFrequencyBand() {
+ int band = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_FREQUENCY_BAND, WifiManager.WIFI_FREQUENCY_BAND_AUTO);
+ setFrequencyBand(band, false);
+ }
+
+ private void setSuspendOptimizationsNative(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizationsNative: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
+ if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
+ mWifiNative.setSuspendOptimizations(true);
+ }
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ mWifiNative.setSuspendOptimizations(false);
+ }
+ }
+
+ private void setSuspendOptimizations(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizations: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ }
+ if (DBG) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+ }
+
+ private void setWifiState(int wifiState) {
+ final int previousWifiState = mWifiState.get();
+
+ try {
+ if (wifiState == WIFI_STATE_ENABLED) {
+ mBatteryStats.noteWifiOn();
+ } else if (wifiState == WIFI_STATE_DISABLED) {
+ mBatteryStats.noteWifiOff();
+ }
+ } catch (RemoteException e) {
+ loge("Failed to note battery stats in wifi");
+ }
+
+ mWifiState.set(wifiState);
+
+ if (DBG) log("setWifiState: " + syncGetWifiStateByName());
+
+ final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void setWifiApState(int wifiApState) {
+ final int previousWifiApState = mWifiApState.get();
+
+ try {
+ if (wifiApState == WIFI_AP_STATE_ENABLED) {
+ mBatteryStats.noteWifiOn();
+ } else if (wifiApState == WIFI_AP_STATE_DISABLED) {
+ mBatteryStats.noteWifiOff();
+ }
+ } catch (RemoteException e) {
+ loge("Failed to note battery stats in wifi");
+ }
+
+ // Update state
+ mWifiApState.set(wifiApState);
+
+ if (DBG) log("setWifiApState: " + syncGetWifiApStateByName());
+
+ final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private static final String ID_STR = "id=";
+ private static final String BSSID_STR = "bssid=";
+ private static final String FREQ_STR = "freq=";
+ private static final String LEVEL_STR = "level=";
+ private static final String TSF_STR = "tsf=";
+ private static final String FLAGS_STR = "flags=";
+ private static final String SSID_STR = "ssid=";
+ private static final String DELIMITER_STR = "====";
+ private static final String END_STR = "####";
+
+ /**
+ * Format:
+ *
+ * id=1
+ * bssid=68:7f:76:d7:1a:6e
+ * freq=2412
+ * level=-44
+ * tsf=1344626243700342
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zfdy
+ * ====
+ * id=2
+ * bssid=68:5f:74:d7:1a:6f
+ * freq=5180
+ * level=-73
+ * tsf=1344626243700373
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zuby
+ * ====
+ */
+ private void setScanResults() {
+ String bssid = "";
+ int level = 0;
+ int freq = 0;
+ long tsf = 0;
+ String flags = "";
+ WifiSsid wifiSsid = null;
+ String scanResults;
+ String tmpResults;
+ StringBuffer scanResultsBuf = new StringBuffer();
+ int sid = 0;
+
+ while (true) {
+ tmpResults = mWifiNative.scanResults(sid);
+ if (TextUtils.isEmpty(tmpResults)) break;
+ scanResultsBuf.append(tmpResults);
+ scanResultsBuf.append("\n");
+ String[] lines = tmpResults.split("\n");
+ sid = -1;
+ for (int i=lines.length - 1; i >= 0; i--) {
+ if (lines[i].startsWith(END_STR)) {
+ break;
+ } else if (lines[i].startsWith(ID_STR)) {
+ try {
+ sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
+ } catch (NumberFormatException e) {
+ // Nothing to do
+ }
+ break;
+ }
+ }
+ if (sid == -1) break;
+ }
+
+ scanResults = scanResultsBuf.toString();
+ if (TextUtils.isEmpty(scanResults)) {
+ return;
+ }
+
+ // note that all these splits and substrings keep references to the original
+ // huge string buffer while the amount we really want is generally pretty small
+ // so make copies instead (one example b/11087956 wasted 400k of heap here).
+ synchronized(mScanResultCache) {
+ mScanResults = new ArrayList<ScanResult>();
+ String[] lines = scanResults.split("\n");
+ final int bssidStrLen = BSSID_STR.length();
+ final int flagLen = FLAGS_STR.length();
+
+ for (String line : lines) {
+ if (line.startsWith(BSSID_STR)) {
+ bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);
+ } else if (line.startsWith(FREQ_STR)) {
+ try {
+ freq = Integer.parseInt(line.substring(FREQ_STR.length()));
+ } catch (NumberFormatException e) {
+ freq = 0;
+ }
+ } else if (line.startsWith(LEVEL_STR)) {
+ try {
+ level = Integer.parseInt(line.substring(LEVEL_STR.length()));
+ /* some implementations avoid negative values by adding 256
+ * so we need to adjust for that here.
+ */
+ if (level > 0) level -= 256;
+ } catch(NumberFormatException e) {
+ level = 0;
+ }
+ } else if (line.startsWith(TSF_STR)) {
+ try {
+ tsf = Long.parseLong(line.substring(TSF_STR.length()));
+ } catch (NumberFormatException e) {
+ tsf = 0;
+ }
+ } else if (line.startsWith(FLAGS_STR)) {
+ flags = new String(line.getBytes(), flagLen, line.length() - flagLen);
+ } else if (line.startsWith(SSID_STR)) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ line.substring(SSID_STR.length()));
+ } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
+ if (bssid != null) {
+ String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+ String key = bssid + ssid;
+ ScanResult scanResult = mScanResultCache.get(key);
+ if (scanResult != null) {
+ scanResult.level = level;
+ scanResult.wifiSsid = wifiSsid;
+ // Keep existing API
+ scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :
+ WifiSsid.NONE;
+ scanResult.capabilities = flags;
+ scanResult.frequency = freq;
+ scanResult.timestamp = tsf;
+ } else {
+ scanResult =
+ new ScanResult(
+ wifiSsid, bssid, flags, level, freq, tsf);
+ mScanResultCache.put(key, scanResult);
+ }
+ mScanResults.add(scanResult);
+ }
+ bssid = null;
+ level = 0;
+ freq = 0;
+ tsf = 0;
+ flags = "";
+ wifiSsid = null;
+ }
+ }
+ }
+ }
+
+ /*
+ * Fetch RSSI and linkspeed on current connection
+ */
+ private void fetchRssiAndLinkSpeedNative() {
+ int newRssi = -1;
+ int newLinkSpeed = -1;
+
+ String signalPoll = mWifiNative.signalPoll();
+
+ if (signalPoll != null) {
+ String[] lines = signalPoll.split("\n");
+ for (String line : lines) {
+ String[] prop = line.split("=");
+ if (prop.length < 2) continue;
+ try {
+ if (prop[0].equals("RSSI")) {
+ newRssi = Integer.parseInt(prop[1]);
+ } else if (prop[0].equals("LINKSPEED")) {
+ newLinkSpeed = Integer.parseInt(prop[1]);
+ }
+ } catch (NumberFormatException e) {
+ //Ignore, defaults on rssi and linkspeed are assigned
+ }
+ }
+ }
+
+ if (newRssi != -1 && MIN_RSSI < newRssi && newRssi < MAX_RSSI) { // screen out invalid values
+ /* some implementations avoid negative values by adding 256
+ * so we need to adjust for that here.
+ */
+ if (newRssi > 0) newRssi -= 256;
+ mWifiInfo.setRssi(newRssi);
+ /*
+ * Rather then sending the raw RSSI out every time it
+ * changes, we precalculate the signal level that would
+ * be displayed in the status bar, and only send the
+ * broadcast if that much more coarse-grained number
+ * changes. This cuts down greatly on the number of
+ * broadcasts, at the cost of not informing others
+ * interested in RSSI of all the changes in signal
+ * level.
+ */
+ int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS);
+ if (newSignalLevel != mLastSignalLevel) {
+ sendRssiChangeBroadcast(newRssi);
+ }
+ mLastSignalLevel = newSignalLevel;
+ } else {
+ mWifiInfo.setRssi(MIN_RSSI);
+ }
+
+ if (newLinkSpeed != -1) {
+ mWifiInfo.setLinkSpeed(newLinkSpeed);
+ }
+ }
+
+ /*
+ * Fetch TX packet counters on current connection
+ */
+ private void fetchPktcntNative(RssiPacketCountInfo info) {
+ String pktcntPoll = mWifiNative.pktcntPoll();
+
+ if (pktcntPoll != null) {
+ String[] lines = pktcntPoll.split("\n");
+ for (String line : lines) {
+ String[] prop = line.split("=");
+ if (prop.length < 2) continue;
+ try {
+ if (prop[0].equals("TXGOOD")) {
+ info.txgood = Integer.parseInt(prop[1]);
+ } else if (prop[0].equals("TXBAD")) {
+ info.txbad = Integer.parseInt(prop[1]);
+ }
+ } catch (NumberFormatException e) {
+ //Ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates mLinkProperties by merging information from various sources.
+ *
+ * This is needed because the information in mLinkProperties comes from multiple sources (DHCP,
+ * netlink, static configuration, ...). When one of these sources of information has updated
+ * link properties, we can't just assign them to mLinkProperties or we'd lose track of the
+ * information that came from other sources. Instead, when one of those sources has new
+ * information, we update the object that tracks the information from that source and then
+ * call this method to apply the change to mLinkProperties.
+ *
+ * The information in mLinkProperties is currently obtained as follows:
+ * - Interface name: set in the constructor.
+ * - IPv4 and IPv6 addresses: netlink, via mInterfaceObserver.
+ * - IPv4 routes, DNS servers, and domains: DHCP.
+ * - HTTP proxy: the wifi config store.
+ */
+ private void updateLinkProperties() {
+ LinkProperties newLp = new LinkProperties();
+
+ // Interface name and proxy are locally configured.
+ newLp.setInterfaceName(mInterfaceName);
+ newLp.setHttpProxy(mWifiConfigStore.getProxyProperties(mLastNetworkId));
+
+ // IPv4 and IPv6 addresses come from netlink.
+ newLp.setLinkAddresses(mNetlinkLinkProperties.getLinkAddresses());
+
+ // For now, routing and DNS only come from DHCP or static configuration. In the future,
+ // we'll need to merge IPv6 DNS servers and domains coming from netlink.
+ synchronized (mDhcpResultsLock) {
+ // Even when we're using static configuration, we don't need to look at the config
+ // store, because static IP configuration also populates mDhcpResults.
+ if ((mDhcpResults != null) && (mDhcpResults.linkProperties != null)) {
+ LinkProperties lp = mDhcpResults.linkProperties;
+ for (RouteInfo route: lp.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ for (InetAddress dns: lp.getDnses()) {
+ newLp.addDns(dns);
+ }
+ newLp.setDomains(lp.getDomains());
+ }
+ }
+
+ // If anything has changed, and we're already connected, send out a notification.
+ // If we're still connecting, apps will be notified when we connect.
+ if (!newLp.equals(mLinkProperties)) {
+ if (DBG) {
+ log("Link configuration changed for netId: " + mLastNetworkId
+ + " old: " + mLinkProperties + "new: " + newLp);
+ }
+ mLinkProperties = newLp;
+ if (getNetworkDetailedState() == DetailedState.CONNECTED) {
+ sendLinkConfigurationChangedBroadcast();
+ }
+ }
+ }
+
+ /**
+ * Clears all our link properties.
+ */
+ private void clearLinkProperties() {
+ // If the network used DHCP, clear the LinkProperties we stored in the config store.
+ if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+ mWifiConfigStore.clearLinkProperties(mLastNetworkId);
+ }
+
+ // Clear the link properties obtained from DHCP and netlink.
+ synchronized(mDhcpResultsLock) {
+ if (mDhcpResults != null && mDhcpResults.linkProperties != null) {
+ mDhcpResults.linkProperties.clear();
+ }
+ }
+ mNetlinkLinkProperties.clear();
+
+ // Now clear the merged link properties.
+ mLinkProperties.clear();
+ }
+
+ private int getMaxDhcpRetries() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+ DEFAULT_MAX_DHCP_RETRIES);
+ }
+
+ private void sendScanResultsAvailableBroadcast() {
+ noteScanEnd();
+ Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendRssiChangeBroadcast(final int newRssi) {
+ Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendNetworkStateChangeBroadcast(String bssid) {
+ Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
+ intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties));
+ if (bssid != null)
+ intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+ if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK ||
+ mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
+ intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo));
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendLinkConfigurationChangedBroadcast() {
+ Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties(mLinkProperties));
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
+ Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Record the detailed state of a network.
+ * @param state the new {@code DetailedState}
+ */
+ private void setNetworkDetailedState(NetworkInfo.DetailedState state) {
+ if (DBG) {
+ log("setDetailed state, old ="
+ + mNetworkInfo.getDetailedState() + " and new state=" + state);
+ }
+
+ if (state != mNetworkInfo.getDetailedState()) {
+ mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID());
+ }
+ }
+
+ private DetailedState getNetworkDetailedState() {
+ return mNetworkInfo.getDetailedState();
+ }
+
+
+ private SupplicantState handleSupplicantStateChange(Message message) {
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ // Supplicant state change
+ // [31-13] Reserved for future use
+ // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+ // 50023 supplicant_state_changed (custom|1|5)
+ mWifiInfo.setSupplicantState(state);
+ // Network id is only valid when we start connecting
+ if (SupplicantState.isConnecting(state)) {
+ mWifiInfo.setNetworkId(stateChangeResult.networkId);
+ } else {
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+ }
+
+ mWifiInfo.setBSSID(stateChangeResult.BSSID);
+ mWifiInfo.setSSID(stateChangeResult.wifiSsid);
+
+ mSupplicantStateTracker.sendMessage(Message.obtain(message));
+
+ return state;
+ }
+
+ /**
+ * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
+ * using the interface, stopping DHCP & disabling interface
+ */
+ private void handleNetworkDisconnect() {
+ if (DBG) log("Stopping DHCP and clearing IP");
+
+ stopDhcp();
+
+ try {
+ mNwService.clearInterfaceAddresses(mInterfaceName);
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (Exception e) {
+ loge("Failed to clear addresses or disable ipv6" + e);
+ }
+
+ /* Reset data structures */
+ mWifiInfo.setInetAddress(null);
+ mWifiInfo.setBSSID(null);
+ mWifiInfo.setSSID(null);
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+ mWifiInfo.setRssi(MIN_RSSI);
+ mWifiInfo.setLinkSpeed(-1);
+ mWifiInfo.setMeteredHint(false);
+
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
+
+ /* Clear network properties */
+ clearLinkProperties();
+
+ /* send event to CM & network change broadcast */
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ mLastBssid= null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ }
+
+ private void handleSupplicantConnectionLoss() {
+ /* Socket connection can be lost when we do a graceful shutdown
+ * or when the driver is hung. Ensure supplicant is stopped here.
+ */
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ sendSupplicantConnectionChangedBroadcast(false);
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+
+ void handlePreDhcpSetup() {
+ mDhcpActive = true;
+ if (!mBluetoothConnectionActive) {
+ /*
+ * There are problems setting the Wi-Fi driver's power
+ * mode to active when bluetooth coexistence mode is
+ * enabled or sense.
+ * <p>
+ * We set Wi-Fi to active mode when
+ * obtaining an IP address because we've found
+ * compatibility issues with some routers with low power
+ * mode.
+ * <p>
+ * In order for this active power mode to properly be set,
+ * we disable coexistence mode until we're done with
+ * obtaining an IP address. One exception is if we
+ * are currently connected to a headset, since disabling
+ * coexistence would interrupt that connection.
+ */
+ // Disable the coexistence mode
+ mWifiNative.setBluetoothCoexistenceMode(
+ mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+ }
+
+ /* Disable power save and suspend optimizations during DHCP */
+ // Note: The order here is important for now. Brcm driver changes
+ // power settings when we control suspend mode optimizations.
+ // TODO: Remove this comment when the driver is fixed.
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
+ mWifiNative.setPowerSave(false);
+
+ stopBatchedScan();
+
+ /* P2p discovery breaks dhcp, shut it down in order to get through this */
+ Message msg = new Message();
+ msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
+ msg.arg1 = WifiP2pServiceImpl.ENABLED;
+ msg.arg2 = DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE;
+ msg.obj = mDhcpStateMachine;
+ mWifiP2pChannel.sendMessage(msg);
+ }
+
+
+ void startDhcp() {
+ if (mDhcpStateMachine == null) {
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+ mContext, WifiStateMachine.this, mInterfaceName);
+
+ }
+ mDhcpStateMachine.registerForPreDhcpNotification();
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
+ }
+
+ void stopDhcp() {
+ if (mDhcpStateMachine != null) {
+ /* In case we were in middle of DHCP operation restore back powermode */
+ handlePostDhcpSetup();
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
+ }
+ }
+
+ void handlePostDhcpSetup() {
+ /* Restore power save and suspend optimizations */
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
+ mWifiNative.setPowerSave(true);
+
+ mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.BLOCK_DISCOVERY, WifiP2pServiceImpl.DISABLED);
+
+ // Set the coexistence mode back to its default value
+ mWifiNative.setBluetoothCoexistenceMode(
+ mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+
+ mDhcpActive = false;
+
+ startBatchedScan();
+ }
+
+ private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) {
+ mLastSignalLevel = -1; // force update of signal strength
+ mReconnectCount = 0; //Reset IP failure tracking
+ synchronized (mDhcpResultsLock) {
+ mDhcpResults = dhcpResults;
+ }
+ LinkProperties linkProperties = dhcpResults.linkProperties;
+ mWifiConfigStore.setLinkProperties(mLastNetworkId, new LinkProperties(linkProperties));
+ InetAddress addr = null;
+ Iterator<InetAddress> addrs = linkProperties.getAddresses().iterator();
+ if (addrs.hasNext()) {
+ addr = addrs.next();
+ }
+ mWifiInfo.setInetAddress(addr);
+ mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());
+ updateLinkProperties();
+ }
+
+ private void handleFailedIpConfiguration() {
+ loge("IP configuration failed");
+
+ mWifiInfo.setInetAddress(null);
+ mWifiInfo.setMeteredHint(false);
+ /**
+ * If we've exceeded the maximum number of retries for DHCP
+ * to a given network, disable the network
+ */
+ int maxRetries = getMaxDhcpRetries();
+ // maxRetries == 0 means keep trying forever
+ if (maxRetries > 0 && ++mReconnectCount > maxRetries) {
+ loge("Failed " +
+ mReconnectCount + " times, Disabling " + mLastNetworkId);
+ mWifiConfigStore.disableNetwork(mLastNetworkId,
+ WifiConfiguration.DISABLED_DHCP_FAILURE);
+ mReconnectCount = 0;
+ }
+
+ /* DHCP times out after about 30 seconds, we do a
+ * disconnect and an immediate reconnect to try again
+ */
+ mWifiNative.disconnect();
+ mWifiNative.reconnect();
+ }
+
+ /* Current design is to not set the config on a running hostapd but instead
+ * stop and start tethering when user changes config on a running access point
+ *
+ * TODO: Add control channel setup through hostapd that allows changing config
+ * on a running daemon
+ */
+ private void startSoftApWithConfig(final WifiConfiguration config) {
+ // start hostapd on a seperate thread
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ mNwService.startAccessPoint(config, mInterfaceName);
+ } catch (Exception e) {
+ loge("Exception in softap start " + e);
+ try {
+ mNwService.stopAccessPoint(mInterfaceName);
+ mNwService.startAccessPoint(config, mInterfaceName);
+ } catch (Exception e1) {
+ loge("Exception in softap re-start " + e1);
+ sendMessage(CMD_START_AP_FAILURE);
+ return;
+ }
+ }
+ if (DBG) log("Soft AP start successful");
+ sendMessage(CMD_START_AP_SUCCESS);
+ }
+ }).start();
+ }
+
+ /********************************************************
+ * HSM states
+ *******************************************************/
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mWifiP2pChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ } else {
+ loge("WifiP2pService connection failure, error=" + message.arg1);
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ loge("WifiP2pService channel lost, message.arg1 =" + message.arg1);
+ //TODO: Re-establish connection to state machine after a delay
+ //mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger());
+ break;
+ case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
+ mBluetoothConnectionActive = (message.arg1 !=
+ BluetoothAdapter.STATE_DISCONNECTED);
+ break;
+ /* Synchronous call returns */
+ case CMD_PING_SUPPLICANT:
+ case CMD_ENABLE_NETWORK:
+ case CMD_ADD_OR_UPDATE_NETWORK:
+ case CMD_REMOVE_NETWORK:
+ case CMD_SAVE_CONFIG:
+ replyToMessage(message, message.what, FAILURE);
+ break;
+ case CMD_GET_CONFIGURED_NETWORKS:
+ replyToMessage(message, message.what, (List<WifiConfiguration>) null);
+ break;
+ case CMD_ENABLE_RSSI_POLL:
+ mEnableRssiPolling = (message.arg1 == 1);
+ break;
+ case CMD_ENABLE_BACKGROUND_SCAN:
+ mEnableBackgroundScan = (message.arg1 == 1);
+ break;
+ case CMD_SET_HIGH_PERF_MODE:
+ if (message.arg1 == 1) {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
+ }
+ break;
+ case CMD_BOOT_COMPLETED:
+ String countryCode = mPersistedCountryCode;
+ if (TextUtils.isEmpty(countryCode) == false) {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE,
+ countryCode);
+ // it may be that the state transition that should send this info
+ // to the driver happened between mPersistedCountryCode getting set
+ // and now, so simply persisting it here would mean we have sent
+ // nothing to the driver. Send the cmd so it might be set now.
+ sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
+ }
+ break;
+ case CMD_SET_BATCHED_SCAN:
+ recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj);
+ break;
+ case CMD_POLL_BATCHED_SCAN:
+ handleBatchedScanPollRequest();
+ break;
+ case CMD_START_NEXT_BATCHED_SCAN:
+ startNextBatchedScan();
+ break;
+ /* Discard */
+ case CMD_START_SCAN:
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT_FAILED:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_DELAYED_STOP_DRIVER:
+ case CMD_DRIVER_START_TIMED_OUT:
+ case CMD_START_AP:
+ case CMD_START_AP_SUCCESS:
+ case CMD_START_AP_FAILURE:
+ case CMD_STOP_AP:
+ case CMD_TETHER_STATE_CHANGE:
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ case CMD_DISCONNECT:
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RELOAD_TLS_AND_RECONNECT:
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case CMD_BLACKLIST_NETWORK:
+ case CMD_CLEAR_BLACKLIST:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_RSSI_POLL:
+ case CMD_ENABLE_ALL_NETWORKS:
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ /* Handled by WifiApConfigStore */
+ case CMD_SET_AP_CONFIG:
+ case CMD_SET_AP_CONFIG_COMPLETED:
+ case CMD_REQUEST_AP_CONFIG:
+ case CMD_RESPONSE_AP_CONFIG:
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ case CMD_NO_NETWORKS_PERIODIC_SCAN:
+ case CMD_DISABLE_P2P_RSP:
+ break;
+ case DhcpStateMachine.CMD_ON_QUIT:
+ mDhcpStateMachine = null;
+ break;
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ mSuspendWakeLock.release();
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
+ }
+ break;
+ case WifiMonitor.DRIVER_HUNG_EVENT:
+ setSupplicantRunning(false);
+ setSupplicantRunning(true);
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.FORGET_NETWORK:
+ replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.SAVE_NETWORK:
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.START_WPS:
+ replyToMessage(message, WifiManager.WPS_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.CANCEL_WPS:
+ replyToMessage(message, WifiManager.CANCEL_WPS_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.DISABLE_NETWORK:
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH:
+ replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
+ break;
+ case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
+ mTemporarilyDisconnectWifi = (message.arg1 == 1);
+ replyToMessage(message, WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
+ break;
+ case CMD_IP_ADDRESS_UPDATED:
+ // addLinkAddress is a no-op if called more than once with the same address.
+ if (mNetlinkLinkProperties.addLinkAddress((LinkAddress) message.obj)) {
+ updateLinkProperties();
+ }
+ break;
+ case CMD_IP_ADDRESS_REMOVED:
+ if (mNetlinkLinkProperties.removeLinkAddress((LinkAddress) message.obj)) {
+ updateLinkProperties();
+ }
+ break;
+ default:
+ loge("Error! unhandled message" + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ class InitialState extends State {
+ @Override
+ public void enter() {
+ mWifiNative.unloadDriver();
+
+ if (mWifiP2pChannel == null) {
+ mWifiP2pChannel = new AsyncChannel();
+ mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger());
+ }
+
+ if (mWifiApConfigChannel == null) {
+ mWifiApConfigChannel = new AsyncChannel();
+ WifiApConfigStore wifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
+ mContext, getHandler());
+ wifiApConfigStore.loadApConfiguration();
+ mWifiApConfigChannel.connectSync(mContext, getHandler(),
+ wifiApConfigStore.getMessenger());
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_START_SUPPLICANT:
+ if (mWifiNative.loadDriver()) {
+ try {
+ mNwService.wifiFirmwareReload(mInterfaceName, "STA");
+ } catch (Exception e) {
+ loge("Failed to reload STA firmware " + e);
+ // continue
+ }
+
+ try {
+ // A runtime crash can leave the interface up and
+ // this affects connectivity when supplicant starts up.
+ // Ensure interface is down before a supplicant start.
+ mNwService.setInterfaceDown(mInterfaceName);
+ // Set privacy extensions
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+
+ // IPv6 is enabled only as long as access point is connected since:
+ // - IPv6 addresses and routes stick around after disconnection
+ // - kernel is unaware when connected and fails to start IPv6 negotiation
+ // - kernel can start autoconfiguration when 802.1x is not complete
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Unable to change interface settings: " + re);
+ } catch (IllegalStateException ie) {
+ loge("Unable to change interface settings: " + ie);
+ }
+
+ /* Stop a running supplicant after a runtime restart
+ * Avoids issues with drivers that do not handle interface down
+ * on a running supplicant properly.
+ */
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ if(mWifiNative.startSupplicant(mP2pSupported)) {
+ setWifiState(WIFI_STATE_ENABLING);
+ if (DBG) log("Supplicant start successful");
+ mWifiMonitor.startMonitoring();
+ transitionTo(mSupplicantStartingState);
+ } else {
+ loge("Failed to start supplicant!");
+ }
+ } else {
+ loge("Failed to load driver");
+ }
+ break;
+ case CMD_START_AP:
+ if (mWifiNative.loadDriver()) {
+ setWifiApState(WIFI_AP_STATE_ENABLING);
+ transitionTo(mSoftApStartingState);
+ } else {
+ loge("Failed to load driver for softap");
+ }
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SupplicantStartingState extends State {
+ private void initializeWpsDetails() {
+ String detail;
+ detail = SystemProperties.get("ro.product.name", "");
+ if (!mWifiNative.setDeviceName(detail)) {
+ loge("Failed to set device name " + detail);
+ }
+ detail = SystemProperties.get("ro.product.manufacturer", "");
+ if (!mWifiNative.setManufacturer(detail)) {
+ loge("Failed to set manufacturer " + detail);
+ }
+ detail = SystemProperties.get("ro.product.model", "");
+ if (!mWifiNative.setModelName(detail)) {
+ loge("Failed to set model name " + detail);
+ }
+ detail = SystemProperties.get("ro.product.model", "");
+ if (!mWifiNative.setModelNumber(detail)) {
+ loge("Failed to set model number " + detail);
+ }
+ detail = SystemProperties.get("ro.serialno", "");
+ if (!mWifiNative.setSerialNumber(detail)) {
+ loge("Failed to set serial number " + detail);
+ }
+ if (!mWifiNative.setConfigMethods("physical_display virtual_push_button")) {
+ loge("Failed to set WPS config methods");
+ }
+ if (!mWifiNative.setDeviceType(mPrimaryDeviceType)) {
+ loge("Failed to set primary device type " + mPrimaryDeviceType);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ if (DBG) log("Supplicant connection established");
+ setWifiState(WIFI_STATE_ENABLED);
+ mSupplicantRestartCount = 0;
+ /* Reset the supplicant state to indicate the supplicant
+ * state is not known at this time */
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ /* Initialize data structures */
+ mLastBssid = null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ mLastSignalLevel = -1;
+
+ mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
+ mWifiConfigStore.loadAndEnableAllNetworks();
+ initializeWpsDetails();
+
+ sendSupplicantConnectionChangedBroadcast(true);
+ transitionTo(mDriverStartedState);
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (++mSupplicantRestartCount <= SUPPLICANT_RESTART_TRIES) {
+ loge("Failed to setup control channel, restart supplicant");
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ transitionTo(mInitialState);
+ sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
+ } else {
+ loge("Failed " + mSupplicantRestartCount +
+ " times to start supplicant, unload driver");
+ mSupplicantRestartCount = 0;
+ setWifiState(WIFI_STATE_UNKNOWN);
+ transitionTo(mInitialState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SupplicantStartedState extends State {
+ @Override
+ public void enter() {
+ /* Wifi is available as long as we have a connection to supplicant */
+ mNetworkInfo.setIsAvailable(true);
+
+ int defaultInterval = mContext.getResources().getInteger(
+ R.integer.config_wifi_supplicant_scan_interval);
+
+ mSupplicantScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+ defaultInterval);
+
+ mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mSupplicantStoppingState);
+ }
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */
+ loge("Connection lost, restart supplicant");
+ handleSupplicantConnectionLoss();
+ handleNetworkDisconnect();
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mInitialState);
+ }
+ sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
+ break;
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ setScanResults();
+ sendScanResultsAvailableBroadcast();
+ mScanResultIsPending = false;
+ break;
+ case CMD_PING_SUPPLICANT:
+ boolean ok = mWifiNative.ping();
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ /* Cannot start soft AP while in client mode */
+ case CMD_START_AP:
+ loge("Failed to start soft AP with a running supplicant");
+ setWifiApState(WIFI_AP_STATE_FAILED);
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ mOperationalMode = message.arg1;
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkInfo.setIsAvailable(false);
+ }
+ }
+
+ class SupplicantStoppingState extends State {
+ @Override
+ public void enter() {
+ /* Send any reset commands to supplicant before shutting it down */
+ handleNetworkDisconnect();
+ if (mDhcpStateMachine != null) {
+ mDhcpStateMachine.doQuit();
+ }
+
+ if (DBG) log("stopping supplicant");
+ mWifiMonitor.stopSupplicant();
+
+ /* Send ourselves a delayed message to indicate failure after a wait time */
+ sendMessageDelayed(obtainMessage(CMD_STOP_SUPPLICANT_FAILED,
+ ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS);
+ setWifiState(WIFI_STATE_DISABLING);
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ loge("Supplicant connection received while stopping");
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (DBG) log("Supplicant connection lost");
+ handleSupplicantConnectionLoss();
+ transitionTo(mInitialState);
+ break;
+ case CMD_STOP_SUPPLICANT_FAILED:
+ if (message.arg1 == mSupplicantStopFailureToken) {
+ loge("Timed out on a supplicant stop, kill and proceed");
+ handleSupplicantConnectionLoss();
+ transitionTo(mInitialState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStartingState extends State {
+ private int mTries;
+ @Override
+ public void enter() {
+ mTries = 1;
+ /* Send ourselves a delayed message to start driver a second time */
+ sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
+ ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ /* If suplicant is exiting out of INTERFACE_DISABLED state into
+ * a state that indicates driver has started, it is ready to
+ * receive driver commands
+ */
+ if (SupplicantState.isDriverActive(state)) {
+ transitionTo(mDriverStartedState);
+ }
+ break;
+ case CMD_DRIVER_START_TIMED_OUT:
+ if (message.arg1 == mDriverStartToken) {
+ if (mTries >= 2) {
+ loge("Failed to start driver after " + mTries);
+ transitionTo(mDriverStoppedState);
+ } else {
+ loge("Driver start failed, retrying");
+ mWakeLock.acquire();
+ mWifiNative.startDriver();
+ mWakeLock.release();
+
+ ++mTries;
+ /* Send ourselves a delayed message to start driver again */
+ sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
+ ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
+ }
+ }
+ break;
+ /* Queue driver commands & connection events */
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStartedState extends State {
+ @Override
+ public void enter() {
+ mIsRunning = true;
+ mInDelayedStop = false;
+ mDelayedStopCounter++;
+ updateBatteryWorkSource(null);
+ /**
+ * Enable bluetooth coexistence scan mode when bluetooth connection is active.
+ * When this mode is on, some of the low-level scan parameters used by the
+ * driver are changed to reduce interference with bluetooth
+ */
+ mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+ /* set country code */
+ setCountryCode();
+ /* set frequency band of operation */
+ setFrequencyBand();
+ /* initialize network state */
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+
+ /* Remove any filtering on Multicast v6 at start */
+ mWifiNative.stopFilteringMulticastV6Packets();
+
+ /* Reset Multicast v4 filtering state */
+ if (mFilteringMulticastV4Packets.get()) {
+ mWifiNative.startFilteringMulticastV4Packets();
+ } else {
+ mWifiNative.stopFilteringMulticastV4Packets();
+ }
+
+ mDhcpActive = false;
+
+ startBatchedScan();
+
+ if (mOperationalMode != CONNECT_MODE) {
+ mWifiNative.disconnect();
+ mWifiConfigStore.disableAllNetworks();
+ if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+ transitionTo(mScanModeState);
+ } else {
+ /* Driver stop may have disabled networks, enable right after start */
+ mWifiConfigStore.enableAllNetworks();
+
+ if (DBG) log("Attempting to reconnect to wifi network ..");
+ mWifiNative.reconnect();
+
+ // Status pulls in the current supplicant state and network connection state
+ // events over the monitor connection. This helps framework sync up with
+ // current supplicant state
+ mWifiNative.status();
+ transitionTo(mDisconnectedState);
+ }
+
+ // We may have missed screen update at boot
+ if (mScreenBroadcastReceived.get() == false) {
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(
+ Context.POWER_SERVICE);
+ handleScreenStateChanged(powerManager.isScreenOn());
+ } else {
+ // Set the right suspend mode settings
+ mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
+ && mUserWantsSuspendOpt.get());
+ }
+ mWifiNative.setPowerSave(true);
+
+ if (mP2pSupported) {
+ if (mOperationalMode == CONNECT_MODE) {
+ mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
+ } else {
+ // P2P statemachine starts in disabled state, and is not enabled until
+ // CMD_ENABLE_P2P is sent from here; so, nothing needs to be done to
+ // keep it disabled.
+ }
+ }
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_START_SCAN:
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
+ break;
+ case CMD_SET_BATCHED_SCAN:
+ if (recordBatchedScanSettings(message.arg1, message.arg2,
+ (Bundle)message.obj)) {
+ startBatchedScan();
+ }
+ break;
+ case CMD_SET_COUNTRY_CODE:
+ String country = (String) message.obj;
+ if (DBG) log("set country code " + country);
+ if (country != null) {
+ country = country.toUpperCase(Locale.ROOT);
+ if (mLastSetCountryCode == null
+ || country.equals(mLastSetCountryCode) == false) {
+ if (mWifiNative.setCountryCode(country)) {
+ mLastSetCountryCode = country;
+ } else {
+ loge("Failed to set country code " + country);
+ }
+ }
+ }
+ break;
+ case CMD_SET_FREQUENCY_BAND:
+ int band = message.arg1;
+ if (DBG) log("set frequency band " + band);
+ if (mWifiNative.setBand(band)) {
+ mFrequencyBand.set(band);
+ // flush old data - like scan results
+ mWifiNative.bssFlush();
+ //Fetch the latest scan results when frequency band is set
+ startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
+ } else {
+ loge("Failed to set frequency band " + band);
+ }
+ break;
+ case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
+ mBluetoothConnectionActive = (message.arg1 !=
+ BluetoothAdapter.STATE_DISCONNECTED);
+ mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+ break;
+ case CMD_STOP_DRIVER:
+ int mode = message.arg1;
+
+ /* Already doing a delayed stop */
+ if (mInDelayedStop) {
+ if (DBG) log("Already in delayed stop");
+ break;
+ }
+ /* disconnect right now, but leave the driver running for a bit */
+ mWifiConfigStore.disableAllNetworks();
+
+ mInDelayedStop = true;
+ mDelayedStopCounter++;
+ if (DBG) log("Delayed stop message " + mDelayedStopCounter);
+
+ /* send regular delayed shut down */
+ Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
+ driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
+ mDriverStopIntent = PendingIntent.getBroadcast(mContext,
+ DRIVER_STOP_REQUEST, driverStopIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ + mDriverStopDelayMs, mDriverStopIntent);
+ break;
+ case CMD_START_DRIVER:
+ if (mInDelayedStop) {
+ mInDelayedStop = false;
+ mDelayedStopCounter++;
+ mAlarmManager.cancel(mDriverStopIntent);
+ if (DBG) log("Delayed stop ignored due to start");
+ if (mOperationalMode == CONNECT_MODE) {
+ mWifiConfigStore.enableAllNetworks();
+ }
+ }
+ break;
+ case CMD_DELAYED_STOP_DRIVER:
+ if (DBG) log("delayed stop " + message.arg1 + " " + mDelayedStopCounter);
+ if (message.arg1 != mDelayedStopCounter) break;
+ if (getCurrentState() != mDisconnectedState) {
+ mWifiNative.disconnect();
+ handleNetworkDisconnect();
+ }
+ mWakeLock.acquire();
+ mWifiNative.stopDriver();
+ mWakeLock.release();
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mDriverStoppingState);
+ }
+ break;
+ case CMD_START_PACKET_FILTERING:
+ if (message.arg1 == MULTICAST_V6) {
+ mWifiNative.startFilteringMulticastV6Packets();
+ } else if (message.arg1 == MULTICAST_V4) {
+ mWifiNative.startFilteringMulticastV4Packets();
+ } else {
+ loge("Illegal arugments to CMD_START_PACKET_FILTERING");
+ }
+ break;
+ case CMD_STOP_PACKET_FILTERING:
+ if (message.arg1 == MULTICAST_V6) {
+ mWifiNative.stopFilteringMulticastV6Packets();
+ } else if (message.arg1 == MULTICAST_V4) {
+ mWifiNative.stopFilteringMulticastV4Packets();
+ } else {
+ loge("Illegal arugments to CMD_STOP_PACKET_FILTERING");
+ }
+ break;
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
+ mSuspendWakeLock.release();
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
+ }
+ break;
+ case CMD_SET_HIGH_PERF_MODE:
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
+ }
+ break;
+ case CMD_ENABLE_TDLS:
+ if (message.obj != null) {
+ String remoteAddress = (String) message.obj;
+ boolean enable = (message.arg1 == 1);
+ mWifiNative.startTdls(remoteAddress, enable);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ @Override
+ public void exit() {
+ mIsRunning = false;
+ updateBatteryWorkSource(null);
+ mScanResults = new ArrayList<ScanResult>();
+
+ stopBatchedScan();
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ noteScanEnd(); // wrap up any pending request.
+
+ mLastSetCountryCode = null;
+ }
+ }
+
+ class WaitForP2pDisableState extends State {
+ private State mTransitionToState;
+ @Override
+ public void enter() {
+ switch (getCurrentMessage().what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ mTransitionToState = mInitialState;
+ break;
+ case CMD_DELAYED_STOP_DRIVER:
+ mTransitionToState = mDriverStoppingState;
+ break;
+ case CMD_STOP_SUPPLICANT:
+ mTransitionToState = mSupplicantStoppingState;
+ break;
+ default:
+ mTransitionToState = mDriverStoppingState;
+ break;
+ }
+ mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiStateMachine.CMD_DISABLE_P2P_RSP:
+ transitionTo(mTransitionToState);
+ break;
+ /* Defer wifi start/shut and driver commands */
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStoppingState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ if (state == SupplicantState.INTERFACE_DISABLED) {
+ transitionTo(mDriverStoppedState);
+ }
+ break;
+ /* Queue driver commands */
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStoppedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ // A WEXT bug means that we can be back to driver started state
+ // unexpectedly
+ if (SupplicantState.isDriverActive(state)) {
+ transitionTo(mDriverStartedState);
+ }
+ break;
+ case CMD_START_DRIVER:
+ mWakeLock.acquire();
+ mWifiNative.startDriver();
+ mWakeLock.release();
+ transitionTo(mDriverStartingState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ScanModeState extends State {
+ private int mLastOperationMode;
+ @Override
+ public void enter() {
+ mLastOperationMode = mOperationalMode;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 == CONNECT_MODE) {
+
+ if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ setWifiState(WIFI_STATE_ENABLED);
+ // Load and re-enable networks when going back to enabled state
+ // This is essential for networks to show up after restore
+ mWifiConfigStore.loadAndEnableAllNetworks();
+ mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P);
+ } else {
+ mWifiConfigStore.enableAllNetworks();
+ }
+
+ mWifiNative.reconnect();
+
+ mOperationalMode = CONNECT_MODE;
+ transitionTo(mDisconnectedState);
+ } else {
+ // Nothing to do
+ return HANDLED;
+ }
+ break;
+ // Handle scan. All the connection related commands are
+ // handled only in ConnectModeState
+ case CMD_START_SCAN:
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ConnectModeState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ WifiConfiguration config;
+ boolean ok;
+ switch(message.what) {
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
+ break;
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ // A driver/firmware hang can now put the interface in a down state.
+ // We detect the interface going down and recover from it
+ if (!SupplicantState.isDriverActive(state)) {
+ if (mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
+ handleNetworkDisconnect();
+ }
+ log("Detected an interface down, restart driver");
+ transitionTo(mDriverStoppedState);
+ sendMessage(CMD_START_DRIVER);
+ break;
+ }
+
+ // Supplicant can fail to report a NETWORK_DISCONNECTION_EVENT
+ // when authentication times out after a successful connection,
+ // we can figure this from the supplicant state. If supplicant
+ // state is DISCONNECTED, but the mNetworkInfo says we are not
+ // disconnected, we need to handle a disconnection
+ if (state == SupplicantState.DISCONNECTED &&
+ mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
+ if (DBG) log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ }
+ break;
+ case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
+ if (message.arg1 == 1) {
+ mWifiNative.disconnect();
+ mTemporarilyDisconnectWifi = true;
+ } else {
+ mWifiNative.reconnect();
+ mTemporarilyDisconnectWifi = false;
+ }
+ break;
+ case CMD_ADD_OR_UPDATE_NETWORK:
+ config = (WifiConfiguration) message.obj;
+ replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
+ mWifiConfigStore.addOrUpdateNetwork(config));
+ break;
+ case CMD_REMOVE_NETWORK:
+ ok = mWifiConfigStore.removeNetwork(message.arg1);
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ case CMD_ENABLE_NETWORK:
+ ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ case CMD_ENABLE_ALL_NETWORKS:
+ long time = android.os.SystemClock.elapsedRealtime();
+ if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
+ mWifiConfigStore.enableAllNetworks();
+ mLastEnableAllNetworksTime = time;
+ }
+ break;
+ case WifiManager.DISABLE_NETWORK:
+ if (mWifiConfigStore.disableNetwork(message.arg1,
+ WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case CMD_BLACKLIST_NETWORK:
+ mWifiNative.addToBlacklist((String)message.obj);
+ break;
+ case CMD_CLEAR_BLACKLIST:
+ mWifiNative.clearBlacklist();
+ break;
+ case CMD_SAVE_CONFIG:
+ ok = mWifiConfigStore.saveConfig();
+ replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
+
+ // Inform the backup manager about a data change
+ IBackupManager ibm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (ibm != null) {
+ try {
+ ibm.dataChanged("com.android.providers.settings");
+ } catch (Exception e) {
+ // Try again later
+ }
+ }
+ break;
+ case CMD_GET_CONFIGURED_NETWORKS:
+ replyToMessage(message, message.what,
+ mWifiConfigStore.getConfiguredNetworks());
+ break;
+ /* Do a redundant disconnect without transition */
+ case CMD_DISCONNECT:
+ mWifiNative.disconnect();
+ break;
+ case CMD_RECONNECT:
+ mWifiNative.reconnect();
+ break;
+ case CMD_REASSOCIATE:
+ mWifiNative.reassociate();
+ break;
+ case CMD_RELOAD_TLS_AND_RECONNECT:
+ if (mWifiConfigStore.needsUnlockedKeyStore()) {
+ logd("Reconnecting to give a chance to un-connected TLS networks");
+ mWifiNative.disconnect();
+ mWifiNative.reconnect();
+ }
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ /* The connect message can contain a network id passed as arg1 on message or
+ * or a config passed as obj on message.
+ * For a new network, a config is passed to create and connect.
+ * For an existing network, a network id is passed
+ */
+ int netId = message.arg1;
+ config = (WifiConfiguration) message.obj;
+
+ /* Save the network config */
+ if (config != null) {
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ netId = result.getNetworkId();
+ }
+
+ if (mWifiConfigStore.selectNetwork(netId) &&
+ mWifiNative.reconnect()) {
+ /* The state tracker handles enabling networks upon completion/failure */
+ mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
+ /* Expect a disconnection from the old connection */
+ transitionTo(mDisconnectingState);
+ } else {
+ loge("Failed to connect config: " + config + " netId: " + netId);
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+ WifiManager.ERROR);
+ break;
+ }
+ break;
+ case WifiManager.SAVE_NETWORK:
+ config = (WifiConfiguration) message.obj;
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to save network");
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case WifiManager.FORGET_NETWORK:
+ if (mWifiConfigStore.forgetNetwork(message.arg1)) {
+ replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to forget network");
+ replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case WifiManager.START_WPS:
+ WpsInfo wpsInfo = (WpsInfo) message.obj;
+ WpsResult wpsResult;
+ switch (wpsInfo.setup) {
+ case WpsInfo.PBC:
+ wpsResult = mWifiConfigStore.startWpsPbc(wpsInfo);
+ break;
+ case WpsInfo.KEYPAD:
+ wpsResult = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
+ break;
+ case WpsInfo.DISPLAY:
+ wpsResult = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
+ break;
+ default:
+ wpsResult = new WpsResult(Status.FAILURE);
+ loge("Invalid setup for WPS");
+ break;
+ }
+ if (wpsResult.status == Status.SUCCESS) {
+ replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
+ transitionTo(mWpsRunningState);
+ } else {
+ loge("Failed to start WPS with config " + wpsInfo.toString());
+ replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
+ }
+ break;
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ if (DBG) log("Network connection established");
+ mLastNetworkId = message.arg1;
+ mLastBssid = (String) message.obj;
+
+ mWifiInfo.setBSSID(mLastBssid);
+ mWifiInfo.setNetworkId(mLastNetworkId);
+ /* send event to CM & network change broadcast */
+ setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ transitionTo(mObtainingIpState);
+ break;
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ if (DBG) log("Network connection lost");
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class L2ConnectedState extends State {
+ @Override
+ public void enter() {
+ mRssiPollToken++;
+ if (mEnableRssiPolling) {
+ sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
+ }
+ }
+
+ @Override
+ public void exit() {
+ handleNetworkDisconnect();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ handlePreDhcpSetup();
+ break;
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ handlePostDhcpSetup();
+ if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
+ if (DBG) log("DHCP successful");
+ handleSuccessfulIpConfiguration((DhcpResults) message.obj);
+ transitionTo(mVerifyingLinkState);
+ } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+ if (DBG) log("DHCP failed");
+ handleFailedIpConfiguration();
+ transitionTo(mDisconnectingState);
+ }
+ break;
+ case CMD_DISCONNECT:
+ mWifiNative.disconnect();
+ transitionTo(mDisconnectingState);
+ break;
+ case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
+ if (message.arg1 == 1) {
+ mWifiNative.disconnect();
+ mTemporarilyDisconnectWifi = true;
+ transitionTo(mDisconnectingState);
+ }
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ sendMessage(CMD_DISCONNECT);
+ deferMessage(message);
+ }
+ break;
+ case CMD_START_SCAN:
+ /* Do not attempt to connect when we are already connected */
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
+ break;
+ /* Ignore connection to same network */
+ case WifiManager.CONNECT_NETWORK:
+ int netId = message.arg1;
+ if (mWifiInfo.getNetworkId() == netId) {
+ break;
+ }
+ return NOT_HANDLED;
+ case WifiManager.SAVE_NETWORK:
+ WifiConfiguration config = (WifiConfiguration) message.obj;
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ if (mWifiInfo.getNetworkId() == result.getNetworkId()) {
+ if (result.hasIpChanged()) {
+ log("Reconfiguring IP on connection");
+ transitionTo(mObtainingIpState);
+ }
+ if (result.hasProxyChanged()) {
+ log("Reconfiguring proxy on connection");
+ updateLinkProperties();
+ }
+ }
+
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to save network");
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ /* Ignore */
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ break;
+ case CMD_RSSI_POLL:
+ if (message.arg1 == mRssiPollToken) {
+ // Get Info and continue polling
+ fetchRssiAndLinkSpeedNative();
+ sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
+ mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+ } else {
+ // Polling has completed
+ }
+ break;
+ case CMD_ENABLE_RSSI_POLL:
+ mEnableRssiPolling = (message.arg1 == 1);
+ mRssiPollToken++;
+ if (mEnableRssiPolling) {
+ // first poll
+ fetchRssiAndLinkSpeedNative();
+ sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
+ mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+ }
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH:
+ RssiPacketCountInfo info = new RssiPacketCountInfo();
+ fetchRssiAndLinkSpeedNative();
+ info.rssi = mWifiInfo.getRssi();
+ fetchPktcntNative(info);
+ replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+
+ return HANDLED;
+ }
+ }
+
+ class ObtainingIpState extends State {
+ @Override
+ public void enter() {
+ if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+ // TODO: If we're switching between static IP configuration and DHCP, remove the
+ // static configuration first.
+ startDhcp();
+ } else {
+ // stop any running dhcp before assigning static IP
+ stopDhcp();
+ DhcpResults dhcpResults = new DhcpResults(
+ mWifiConfigStore.getLinkProperties(mLastNetworkId));
+ InterfaceConfiguration ifcg = new InterfaceConfiguration();
+ Iterator<LinkAddress> addrs =
+ dhcpResults.linkProperties.getLinkAddresses().iterator();
+ if (!addrs.hasNext()) {
+ loge("Static IP lacks address");
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ } else {
+ ifcg.setLinkAddress(addrs.next());
+ ifcg.setInterfaceUp();
+ try {
+ mNwService.setInterfaceConfig(mInterfaceName, ifcg);
+ if (DBG) log("Static IP configuration succeeded");
+ sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);
+ } catch (RemoteException re) {
+ loge("Static IP configuration failed: " + re);
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ } catch (IllegalStateException e) {
+ loge("Static IP configuration failed: " + e);
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ }
+ }
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString() + "\n");
+ switch(message.what) {
+ case CMD_STATIC_IP_SUCCESS:
+ handleSuccessfulIpConfiguration((DhcpResults) message.obj);
+ transitionTo(mVerifyingLinkState);
+ break;
+ case CMD_STATIC_IP_FAILURE:
+ handleFailedIpConfiguration();
+ transitionTo(mDisconnectingState);
+ break;
+ case WifiManager.SAVE_NETWORK:
+ deferMessage(message);
+ break;
+ /* Defer any power mode changes since we must keep active power mode at DHCP */
+ case CMD_SET_HIGH_PERF_MODE:
+ deferMessage(message);
+ break;
+ /* Defer scan request since we should not switch to other channels at DHCP */
+ case CMD_START_SCAN:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class VerifyingLinkState extends State {
+ @Override
+ public void enter() {
+ log(getName() + " enter");
+ setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ //stay here
+ log(getName() + " POOR_LINK_DETECTED: no transition");
+ break;
+ case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
+ // Send out a broadcast with the CAPTIVE_PORTAL_CHECK to preserve
+ // existing behaviour. The captive portal check really happens after we
+ // transition into DetailedState.CONNECTED.
+ setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
+ mWifiConfigStore.updateStatus(mLastNetworkId,
+ DetailedState.CAPTIVE_PORTAL_CHECK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ // NOTE: This might look like an odd place to enable IPV6 but this is in
+ // response to transitioning into GOOD_LINK_DETECTED. Similarly, we disable
+ // ipv6 when we transition into POOR_LINK_DETECTED in mConnectedState.
+ try {
+ mNwService.enableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Failed to enable IPv6: " + re);
+ } catch (IllegalStateException e) {
+ loge("Failed to enable IPv6: " + e);
+ }
+
+ log(getName() + " GOOD_LINK_DETECTED: transition to CONNECTED");
+ setNetworkDetailedState(DetailedState.CONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ transitionTo(mConnectedState);
+
+ break;
+ default:
+ if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ConnectedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ if (DBG) log("Watchdog reports poor link");
+ try {
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Failed to disable IPv6: " + re);
+ } catch (IllegalStateException e) {
+ loge("Failed to disable IPv6: " + e);
+ }
+ /* Report a disconnect */
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ transitionTo(mVerifyingLinkState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ /* Request a CS wakelock during transition to mobile */
+ checkAndSetConnectivityInstance();
+ mCm.requestNetworkTransitionWakelock(getName());
+ }
+ }
+
+ class DisconnectingState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ deferMessage(message);
+ }
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ /* If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT
+ * we have missed the network disconnection, transition to mDisconnectedState
+ * and handle the rest of the events there
+ */
+ deferMessage(message);
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DisconnectedState extends State {
+ private boolean mAlarmEnabled = false;
+ /* This is set from the overlay config file or from a secure setting.
+ * A value of 0 disables scanning in the framework.
+ */
+ private long mFrameworkScanIntervalMs;
+
+ private void setScanAlarm(boolean enabled) {
+ if (enabled == mAlarmEnabled) return;
+ if (enabled) {
+ if (mFrameworkScanIntervalMs > 0) {
+ mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + mFrameworkScanIntervalMs,
+ mFrameworkScanIntervalMs,
+ mScanIntent);
+ mAlarmEnabled = true;
+ }
+ } else {
+ mAlarmManager.cancel(mScanIntent);
+ mAlarmEnabled = false;
+ }
+ }
+
+ @Override
+ public void enter() {
+ // We dont scan frequently if this is a temporary disconnect
+ // due to p2p
+ if (mTemporarilyDisconnectWifi) {
+ mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
+ return;
+ }
+
+ mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+ mDefaultFrameworkScanIntervalMs);
+ /*
+ * We initiate background scanning if it is enabled, otherwise we
+ * initiate an infrequent scan that wakes up the device to ensure
+ * a user connects to an access point on the move
+ */
+ if (mEnableBackgroundScan) {
+ /* If a regular scan result is pending, do not initiate background
+ * scan until the scan results are returned. This is needed because
+ * initiating a background scan will cancel the regular scan and
+ * scan results will not be returned until background scanning is
+ * cleared
+ */
+ if (!mScanResultIsPending) {
+ mWifiNative.enableBackgroundScan(true);
+ }
+ } else {
+ setScanAlarm(true);
+ }
+
+ /**
+ * If we have no networks saved, the supplicant stops doing the periodic scan.
+ * The scans are useful to notify the user of the presence of an open network.
+ * Note that these are not wake up scans.
+ */
+ if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case CMD_NO_NETWORKS_PERIODIC_SCAN:
+ if (mP2pConnected.get()) break;
+ if (message.arg1 == mPeriodicScanToken &&
+ mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ sendMessage(CMD_START_SCAN, UNKNOWN_SCAN_SOURCE, 0, (WorkSource) null);
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ break;
+ case WifiManager.FORGET_NETWORK:
+ case CMD_REMOVE_NETWORK:
+ // Set up a delayed message here. After the forget/remove is handled
+ // the handled delayed message will determine if there is a need to
+ // scan and continue
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ ret = NOT_HANDLED;
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ mOperationalMode = message.arg1;
+
+ mWifiConfigStore.disableAllNetworks();
+ if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+
+ transitionTo(mScanModeState);
+ }
+ break;
+ case CMD_ENABLE_BACKGROUND_SCAN:
+ mEnableBackgroundScan = (message.arg1 == 1);
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(true);
+ setScanAlarm(false);
+ } else {
+ mWifiNative.enableBackgroundScan(false);
+ setScanAlarm(true);
+ }
+ break;
+ /* Ignore network disconnect */
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state));
+ /* ConnectModeState does the rest of the handling */
+ ret = NOT_HANDLED;
+ break;
+ case CMD_START_SCAN:
+ /* Disable background scan temporarily during a regular scan */
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(false);
+ }
+ /* Handled in parent state */
+ ret = NOT_HANDLED;
+ break;
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ /* Re-enable background scan when a pending scan result is received */
+ if (mEnableBackgroundScan && mScanResultIsPending) {
+ mWifiNative.enableBackgroundScan(true);
+ }
+ /* Handled in parent state */
+ ret = NOT_HANDLED;
+ break;
+ case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
+ if (mP2pConnected.get()) {
+ int defaultInterval = mContext.getResources().getInteger(
+ R.integer.config_wifi_scan_interval_p2p_connected);
+ long scanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+ defaultInterval);
+ mWifiNative.setScanInterval((int) scanIntervalMs/1000);
+ } else if (mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ if (DBG) log("Turn on scanning after p2p disconnected");
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ if (mTemporarilyDisconnectWifi) {
+ // Drop a third party reconnect/reassociate if STA is
+ // temporarily disconnected for p2p
+ break;
+ } else {
+ // ConnectModeState handles it
+ ret = NOT_HANDLED;
+ }
+ break;
+ default:
+ ret = NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ /* No need for a background scan upon exit from a disconnected state */
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(false);
+ }
+ setScanAlarm(false);
+ }
+ }
+
+ class WpsRunningState extends State {
+ //Tracks the source to provide a reply
+ private Message mSourceMessage;
+ @Override
+ public void enter() {
+ mSourceMessage = Message.obtain(getCurrentMessage());
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiMonitor.WPS_SUCCESS_EVENT:
+ // Ignore intermediate success, wait for full connection
+ break;
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ deferMessage(message);
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+ WifiManager.WPS_OVERLAP_ERROR);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_FAIL_EVENT:
+ //arg1 has the reason for the failure
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_TIMEOUT_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+ WifiManager.WPS_TIMED_OUT);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiManager.START_WPS:
+ replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
+ break;
+ case WifiManager.CANCEL_WPS:
+ if (mWifiNative.cancelWps()) {
+ replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
+ } else {
+ replyToMessage(message, WifiManager.CANCEL_WPS_FAILED, WifiManager.ERROR);
+ }
+ transitionTo(mDisconnectedState);
+ break;
+ /* Defer all commands that can cause connections to a different network
+ * or put the state machine out of connect mode
+ */
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case WifiManager.CONNECT_NETWORK:
+ case CMD_ENABLE_NETWORK:
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ deferMessage(message);
+ break;
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ if (DBG) log("Network connection lost");
+ handleNetworkDisconnect();
+ break;
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ if (DBG) log("Ignore Assoc reject event during WPS Connection");
+ break;
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ // Disregard auth failure events during WPS connection. The
+ // EAP sequence is retried several times, and there might be
+ // failures (especially for wps pin). We will get a WPS_XXX
+ // event at the end of the sequence anyway.
+ if (DBG) log("Ignore auth failure during WPS connection");
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ //Throw away supplicant state changes when WPS is running.
+ //We will start getting supplicant state changes once we get
+ //a WPS success or failure
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mWifiConfigStore.enableAllNetworks();
+ mWifiConfigStore.loadConfiguredNetworks();
+ }
+ }
+
+ class SoftApStartingState extends State {
+ @Override
+ public void enter() {
+ final Message message = getCurrentMessage();
+ if (message.what == CMD_START_AP) {
+ final WifiConfiguration config = (WifiConfiguration) message.obj;
+
+ if (config == null) {
+ mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
+ } else {
+ mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
+ startSoftApWithConfig(config);
+ }
+ } else {
+ throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_TETHER_STATE_CHANGE:
+ deferMessage(message);
+ break;
+ case WifiStateMachine.CMD_RESPONSE_AP_CONFIG:
+ WifiConfiguration config = (WifiConfiguration) message.obj;
+ if (config != null) {
+ startSoftApWithConfig(config);
+ } else {
+ loge("Softap config is null!");
+ sendMessage(CMD_START_AP_FAILURE);
+ }
+ break;
+ case CMD_START_AP_SUCCESS:
+ setWifiApState(WIFI_AP_STATE_ENABLED);
+ transitionTo(mSoftApStartedState);
+ break;
+ case CMD_START_AP_FAILURE:
+ setWifiApState(WIFI_AP_STATE_FAILED);
+ transitionTo(mInitialState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SoftApStartedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_STOP_AP:
+ if (DBG) log("Stopping Soft AP");
+ /* We have not tethered at this point, so we just shutdown soft Ap */
+ try {
+ mNwService.stopAccessPoint(mInterfaceName);
+ } catch(Exception e) {
+ loge("Exception in stopAccessPoint()");
+ }
+ setWifiApState(WIFI_AP_STATE_DISABLED);
+ transitionTo(mInitialState);
+ break;
+ case CMD_START_AP:
+ // Ignore a start on a running access point
+ break;
+ /* Fail client mode operation when soft AP is enabled */
+ case CMD_START_SUPPLICANT:
+ loge("Cannot start supplicant with a running soft AP");
+ setWifiState(WIFI_STATE_UNKNOWN);
+ break;
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (startTethering(stateChange.available)) {
+ transitionTo(mTetheringState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class TetheringState extends State {
+ @Override
+ public void enter() {
+ /* Send ourselves a delayed message to shut down if tethering fails to notify */
+ sendMessageDelayed(obtainMessage(CMD_TETHER_NOTIFICATION_TIMED_OUT,
+ ++mTetherToken, 0), TETHER_NOTIFICATION_TIME_OUT_MSECS);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (isWifiTethered(stateChange.active)) {
+ transitionTo(mTetheredState);
+ }
+ return HANDLED;
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ if (message.arg1 == mTetherToken) {
+ loge("Failed to get tether update, shutdown soft access point");
+ transitionTo(mSoftApStartedState);
+ // Needs to be first thing handled
+ sendMessageAtFrontOfQueue(CMD_STOP_AP);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class TetheredState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (!isWifiTethered(stateChange.active)) {
+ loge("Tethering reports wifi as untethered!, shut down soft Ap");
+ setHostApRunning(null, false);
+ setHostApRunning(null, true);
+ }
+ return HANDLED;
+ case CMD_STOP_AP:
+ if (DBG) log("Untethering before stopping AP");
+ setWifiApState(WIFI_AP_STATE_DISABLING);
+ stopTethering();
+ transitionTo(mUntetheringState);
+ // More work to do after untethering
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class UntetheringState extends State {
+ @Override
+ public void enter() {
+ /* Send ourselves a delayed message to shut down if tethering fails to notify */
+ sendMessageDelayed(obtainMessage(CMD_TETHER_NOTIFICATION_TIMED_OUT,
+ ++mTetherToken, 0), TETHER_NOTIFICATION_TIME_OUT_MSECS);
+
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+
+ /* Wait till wifi is untethered */
+ if (isWifiTethered(stateChange.active)) break;
+
+ transitionTo(mSoftApStartedState);
+ break;
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ if (message.arg1 == mTetherToken) {
+ loge("Failed to get tether update, force stop access point");
+ transitionTo(mSoftApStartedState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ //State machine initiated requests can have replyTo set to null indicating
+ //there are no recepients, we ignore those reply actions
+ private void replyToMessage(Message msg, int what) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, int arg1) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ dstMsg.arg1 = arg1;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, Object obj) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ dstMsg.obj = obj;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ /**
+ * arg2 on the source message has a unique id that needs to be retained in replies
+ * to match the request
+
+ * see WifiManager for details
+ */
+ private Message obtainMessageWithArg2(Message srcMsg) {
+ Message msg = Message.obtain();
+ msg.arg2 = srcMsg.arg2;
+ return msg;
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiTrafficPoller.java b/service/java/com/android/server/wifi/WifiTrafficPoller.java
new file mode 100644
index 000000000..567808659
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2013 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.server.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import static android.net.NetworkInfo.DetailedState.CONNECTED;
+import android.net.TrafficStats;
+import android.net.wifi.WifiManager;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/* Polls for traffic stats and notifies the clients */
+final class WifiTrafficPoller {
+ /**
+ * Interval in milliseconds between polling for traffic
+ * statistics
+ */
+ private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
+
+ private static final int ENABLE_TRAFFIC_STATS_POLL = 1;
+ private static final int TRAFFIC_STATS_POLL = 2;
+ private static final int ADD_CLIENT = 3;
+ private static final int REMOVE_CLIENT = 4;
+
+ private boolean mEnableTrafficStatsPoll = false;
+ private int mTrafficStatsPollToken = 0;
+ private long mTxPkts;
+ private long mRxPkts;
+ /* Tracks last reported data activity */
+ private int mDataActivity;
+
+ private final List<Messenger> mClients = new ArrayList<Messenger>();
+ // err on the side of updating at boot since screen on broadcast may be missed
+ // the first time
+ private AtomicBoolean mScreenOn = new AtomicBoolean(true);
+ private final TrafficHandler mTrafficHandler;
+ private NetworkInfo mNetworkInfo;
+ private final String mInterface;
+
+ WifiTrafficPoller(Context context, String iface) {
+ mInterface = iface;
+ mTrafficHandler = new TrafficHandler();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+
+ context.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ mScreenOn.set(false);
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ mScreenOn.set(true);
+ }
+ evaluateTrafficStatsPolling();
+ }
+ }, filter);
+ }
+
+ void addClient(Messenger client) {
+ Message.obtain(mTrafficHandler, ADD_CLIENT, client).sendToTarget();
+ }
+
+ void removeClient(Messenger client) {
+ Message.obtain(mTrafficHandler, REMOVE_CLIENT, client).sendToTarget();
+ }
+
+
+ private class TrafficHandler extends Handler {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ENABLE_TRAFFIC_STATS_POLL:
+ mEnableTrafficStatsPoll = (msg.arg1 == 1);
+ mTrafficStatsPollToken++;
+ if (mEnableTrafficStatsPoll) {
+ notifyOnDataActivity();
+ sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+ mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+ }
+ break;
+ case TRAFFIC_STATS_POLL:
+ if (msg.arg1 == mTrafficStatsPollToken) {
+ notifyOnDataActivity();
+ sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+ mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+ }
+ break;
+ case ADD_CLIENT:
+ mClients.add((Messenger) msg.obj);
+ break;
+ case REMOVE_CLIENT:
+ mClients.remove(msg.obj);
+ break;
+ }
+
+ }
+ }
+
+ private void evaluateTrafficStatsPolling() {
+ Message msg;
+ if (mNetworkInfo == null) return;
+ if (mNetworkInfo.getDetailedState() == CONNECTED && mScreenOn.get()) {
+ msg = Message.obtain(mTrafficHandler,
+ ENABLE_TRAFFIC_STATS_POLL, 1, 0);
+ } else {
+ msg = Message.obtain(mTrafficHandler,
+ ENABLE_TRAFFIC_STATS_POLL, 0, 0);
+ }
+ msg.sendToTarget();
+ }
+
+ private void notifyOnDataActivity() {
+ long sent, received;
+ long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
+ int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
+
+ mTxPkts = TrafficStats.getTxPackets(mInterface);
+ mRxPkts = TrafficStats.getRxPackets(mInterface);
+
+ if (preTxPkts > 0 || preRxPkts > 0) {
+ sent = mTxPkts - preTxPkts;
+ received = mRxPkts - preRxPkts;
+ if (sent > 0) {
+ dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
+ }
+ if (received > 0) {
+ dataActivity |= WifiManager.DATA_ACTIVITY_IN;
+ }
+
+ if (dataActivity != mDataActivity && mScreenOn.get()) {
+ mDataActivity = dataActivity;
+ for (Messenger client : mClients) {
+ Message msg = Message.obtain();
+ msg.what = WifiManager.DATA_ACTIVITY_NOTIFICATION;
+ msg.arg1 = mDataActivity;
+ try {
+ client.send(msg);
+ } catch (RemoteException e) {
+ // Failed to reach, skip
+ // Client removal is handled in WifiService
+ }
+ }
+ }
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll);
+ pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken);
+ pw.println("mTxPkts " + mTxPkts);
+ pw.println("mRxPkts " + mRxPkts);
+ pw.println("mDataActivity " + mDataActivity);
+ }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiWatchdogStateMachine.java b/service/java/com/android/server/wifi/WifiWatchdogStateMachine.java
new file mode 100644
index 000000000..725036a96
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiWatchdogStateMachine.java
@@ -0,0 +1,1210 @@
+/*
+ * Copyright (C) 2011 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.server.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.wifi.RssiPacketCountInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.LruCache;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+
+/**
+ * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi
+ * connects at L2 layer, the beacons from access point reach the device and it
+ * can maintain a connection, but the application connectivity can be flaky (due
+ * to bigger packet size exchange).
+ * <p>
+ * We now monitor the quality of the last hop on WiFi using packet loss ratio as
+ * an indicator to decide if the link is good enough to switch to Wi-Fi as the
+ * uplink.
+ * <p>
+ * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the
+ * instant packet loss, and record it as per-AP loss-to-rssi statistics. When
+ * the instant packet loss is higher than a threshold, the WiFi watchdog sends a
+ * poor link notification to avoid WiFi connection temporarily.
+ * <p>
+ * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to
+ * bring the WiFi connection back. Once the RSSI is high enough to achieve a
+ * lower packet loss, a good link detection is sent such that the WiFi
+ * connection become available again.
+ * <p>
+ * BSSID roaming has been taken into account. When user is moving across
+ * multiple APs, the WiFi watchdog will detect that and keep watching the
+ * currently connected AP.
+ * <p>
+ * Power impact should be minimal since much of the measurement relies on
+ * passive statistics already being tracked at the driver and the polling is
+ * done when screen is turned on and the RSSI is in a certain range.
+ *
+ * @hide
+ */
+public class WifiWatchdogStateMachine extends StateMachine {
+
+ private static final boolean DBG = false;
+
+ private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
+
+ /* Internal events */
+ private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
+ private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
+ private static final int EVENT_RSSI_CHANGE = BASE + 3;
+ private static final int EVENT_SUPPLICANT_STATE_CHANGE = BASE + 4;
+ private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
+ private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6;
+ private static final int EVENT_BSSID_CHANGE = BASE + 7;
+ private static final int EVENT_SCREEN_ON = BASE + 8;
+ private static final int EVENT_SCREEN_OFF = BASE + 9;
+
+ /* Internal messages */
+ private static final int CMD_RSSI_FETCH = BASE + 11;
+
+ /* Notifications from/to WifiStateMachine */
+ static final int POOR_LINK_DETECTED = BASE + 21;
+ static final int GOOD_LINK_DETECTED = BASE + 22;
+
+ /*
+ * RSSI levels as used by notification icon
+ * Level 4 -55 <= RSSI
+ * Level 3 -66 <= RSSI < -55
+ * Level 2 -77 <= RSSI < -67
+ * Level 1 -88 <= RSSI < -78
+ * Level 0 RSSI < -88
+ */
+
+ /**
+ * WiFi link statistics is monitored and recorded actively below this threshold.
+ * <p>
+ * Larger threshold is more adaptive but increases sampling cost.
+ */
+ private static final int LINK_MONITOR_LEVEL_THRESHOLD = WifiManager.RSSI_LEVELS - 1;
+
+ /**
+ * Remember packet loss statistics of how many BSSIDs.
+ * <p>
+ * Larger size is usually better but requires more space.
+ */
+ private static final int BSSID_STAT_CACHE_SIZE = 20;
+
+ /**
+ * RSSI range of a BSSID statistics.
+ * Within the range, (RSSI -> packet loss %) mappings are stored.
+ * <p>
+ * Larger range is usually better but requires more space.
+ */
+ private static final int BSSID_STAT_RANGE_LOW_DBM = -105;
+
+ /**
+ * See {@link #BSSID_STAT_RANGE_LOW_DBM}.
+ */
+ private static final int BSSID_STAT_RANGE_HIGH_DBM = -45;
+
+ /**
+ * How many consecutive empty data point to trigger a empty-cache detection.
+ * In this case, a preset/default loss value (function on RSSI) is used.
+ * <p>
+ * In normal uses, some RSSI values may never be seen due to channel randomness.
+ * However, the size of such empty RSSI chunk in normal use is generally 1~2.
+ */
+ private static final int BSSID_STAT_EMPTY_COUNT = 3;
+
+ /**
+ * Sample interval for packet loss statistics, in msec.
+ * <p>
+ * Smaller interval is more accurate but increases sampling cost (battery consumption).
+ */
+ private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000;
+
+ /**
+ * Coefficients (alpha) for moving average for packet loss tracking.
+ * Must be within (0.0, 1.0).
+ * <p>
+ * Equivalent number of samples: N = 2 / alpha - 1 .
+ * We want the historic loss to base on more data points to be statistically reliable.
+ * We want the current instant loss to base on less data points to be responsive.
+ */
+ private static final double EXP_COEFFICIENT_RECORD = 0.1;
+
+ /**
+ * See {@link #EXP_COEFFICIENT_RECORD}.
+ */
+ private static final double EXP_COEFFICIENT_MONITOR = 0.5;
+
+ /**
+ * Thresholds for sending good/poor link notifications, in packet loss %.
+ * Good threshold must be smaller than poor threshold.
+ * Use smaller poor threshold to avoid WiFi more aggressively.
+ * Use smaller good threshold to bring back WiFi more conservatively.
+ * <p>
+ * When approaching the boundary, loss ratio jumps significantly within a few dBs.
+ * 50% loss threshold is a good balance between accuracy and reponsiveness.
+ * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily.
+ */
+ private static final double POOR_LINK_LOSS_THRESHOLD = 0.5;
+
+ /**
+ * See {@link #POOR_LINK_LOSS_THRESHOLD}.
+ */
+ private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1;
+
+ /**
+ * Number of samples to confirm before sending a poor link notification.
+ * Response time = confirm_count * sample_interval .
+ * <p>
+ * A smaller threshold improves response speed but may suffer from randomness.
+ * According to experiments, 3~5 are good values to achieve a balance.
+ * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}.
+ */
+ private static final int POOR_LINK_SAMPLE_COUNT = 3;
+
+ /**
+ * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness.
+ * <p>
+ * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive.
+ */
+ private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0;
+
+ /**
+ * When a poor link is detected, we scan over this range (based on current
+ * poor link RSSI) for a target RSSI that satisfies a target packet loss.
+ * Refer to {@link #GOOD_LINK_TARGET}.
+ * <p>
+ * We want range_min not too small to avoid jumping back to WiFi too easily.
+ */
+ private static final int GOOD_LINK_RSSI_RANGE_MIN = 3;
+
+ /**
+ * See {@link #GOOD_LINK_RSSI_RANGE_MIN}.
+ */
+ private static final int GOOD_LINK_RSSI_RANGE_MAX = 20;
+
+ /**
+ * Adaptive good link target to avoid flapping.
+ * When a poor link is detected, a good link target is calculated as follows:
+ * <p>
+ * targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i],
+ * where rssi is within the above GOOD_LINK_RSSI_RANGE.
+ * targetCount = sample_count[i] .
+ * <p>
+ * While WiFi is being avoided, we keep monitoring its signal strength.
+ * Good link notification is sent when we see current RSSI >= targetRSSI
+ * for targetCount consecutive times.
+ * <p>
+ * Index i is incremented each time after a poor link detection.
+ * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago.
+ * <p>
+ * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping.
+ * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve.
+ * Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago).
+ */
+ private static final GoodLinkTarget[] GOOD_LINK_TARGET = {
+ /* rssi_adj, sample_count, reduce_time */
+ new GoodLinkTarget( 0, 3, 30 * 60000 ),
+ new GoodLinkTarget( 3, 5, 5 * 60000 ),
+ new GoodLinkTarget( 6, 10, 1 * 60000 ),
+ new GoodLinkTarget( 9, 30, 0 * 60000 ),
+ };
+
+ /**
+ * The max time to avoid a BSSID, to prevent avoiding forever.
+ * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i]
+ * <p>
+ * It is unusual to experience high packet loss at high RSSI. Something unusual must be
+ * happening (e.g. strong interference). For higher signal strengths, we set the avoidance
+ * time to be low to allow for quick turn around from temporary interference.
+ * <p>
+ * See {@link BssidStatistics#poorLinkDetected}.
+ */
+ private static final MaxAvoidTime[] MAX_AVOID_TIME = {
+ /* max_time, min_rssi */
+ new MaxAvoidTime( 30 * 60000, -200 ),
+ new MaxAvoidTime( 5 * 60000, -70 ),
+ new MaxAvoidTime( 0 * 60000, -55 ),
+ };
+
+ /* Framework related */
+ private Context mContext;
+ private ContentResolver mContentResolver;
+ private WifiManager mWifiManager;
+ private IntentFilter mIntentFilter;
+ private BroadcastReceiver mBroadcastReceiver;
+ private AsyncChannel mWsmChannel = new AsyncChannel();
+ private WifiInfo mWifiInfo;
+ private LinkProperties mLinkProperties;
+
+ /* System settingss related */
+ private static boolean sWifiOnly = false;
+ private boolean mPoorNetworkDetectionEnabled;
+
+ /* Poor link detection related */
+ private LruCache<String, BssidStatistics> mBssidCache =
+ new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE);
+ private int mRssiFetchToken = 0;
+ private int mCurrentSignalLevel;
+ private BssidStatistics mCurrentBssid;
+ private VolumeWeightedEMA mCurrentLoss;
+ private boolean mIsScreenOn = true;
+ private static double sPresetLoss[];
+
+ /* WiFi watchdog state machine related */
+ private DefaultState mDefaultState = new DefaultState();
+ private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
+ private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
+ private NotConnectedState mNotConnectedState = new NotConnectedState();
+ private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
+ private ConnectedState mConnectedState = new ConnectedState();
+ private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
+ private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState();
+ private OnlineState mOnlineState = new OnlineState();
+
+ /**
+ * STATE MAP
+ * Default
+ * / \
+ * Disabled Enabled
+ * / \ \
+ * NotConnected Verifying Connected
+ * /---------\
+ * (all other states)
+ */
+ private WifiWatchdogStateMachine(Context context) {
+ super("WifiWatchdogStateMachine");
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mWsmChannel.connectSync(mContext, getHandler(),
+ mWifiManager.getWifiStateMachineMessenger());
+
+ setupNetworkReceiver();
+
+ // the content observer to listen needs a handler
+ registerForSettingsChanges();
+ registerForWatchdogToggle();
+ addState(mDefaultState);
+ addState(mWatchdogDisabledState, mDefaultState);
+ addState(mWatchdogEnabledState, mDefaultState);
+ addState(mNotConnectedState, mWatchdogEnabledState);
+ addState(mVerifyingLinkState, mWatchdogEnabledState);
+ addState(mConnectedState, mWatchdogEnabledState);
+ addState(mOnlineWatchState, mConnectedState);
+ addState(mLinkMonitoringState, mConnectedState);
+ addState(mOnlineState, mConnectedState);
+
+ if (isWatchdogEnabled()) {
+ setInitialState(mNotConnectedState);
+ } else {
+ setInitialState(mWatchdogDisabledState);
+ }
+ setLogRecSize(25);
+ setLogOnlyTransitions(true);
+ updateSettings();
+ }
+
+ public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
+ ContentResolver contentResolver = context.getContentResolver();
+
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
+
+ // Watchdog is always enabled. Poor network detection can be seperately turned on/off
+ // TODO: Remove this setting & clean up state machine since we always have
+ // watchdog in an enabled state
+ putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
+
+ WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
+ wwsm.start();
+ return wwsm;
+ }
+
+ private void setupNetworkReceiver() {
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+ obtainMessage(EVENT_RSSI_CHANGE,
+ intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
+ } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent);
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
+ } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ sendMessage(EVENT_SCREEN_ON);
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ sendMessage(EVENT_SCREEN_OFF);
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra(
+ WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
+ }
+ }
+ };
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+ }
+
+ /**
+ * Observes the watchdog on/off setting, and takes action when changed.
+ */
+ private void registerForWatchdogToggle() {
+ ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(EVENT_WATCHDOG_TOGGLED);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_ON),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes watchdogs secure setting changes.
+ */
+ private void registerForSettingsChanges() {
+ ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
+ false, contentObserver);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mWifiInfo: [" + mWifiInfo + "]");
+ pw.println("mLinkProperties: [" + mLinkProperties + "]");
+ pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
+ pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
+ }
+
+ private boolean isWatchdogEnabled() {
+ boolean ret = getSettingsGlobalBoolean(
+ mContentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
+ if (DBG) logd("Watchdog enabled " + ret);
+ return ret;
+ }
+
+ private void updateSettings() {
+ if (DBG) logd("Updating secure settings");
+
+ // disable poor network avoidance
+ if (sWifiOnly) {
+ logd("Disabling poor network avoidance for wi-fi only device");
+ mPoorNetworkDetectionEnabled = false;
+ } else {
+ mPoorNetworkDetectionEnabled = getSettingsGlobalBoolean(mContentResolver,
+ Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+ WifiManager.DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED);
+ }
+ }
+
+ /**
+ * Default state, guard for unhandled messages.
+ */
+ class DefaultState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (DBG) logd("Updating wifi-watchdog secure settings");
+ break;
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ break;
+ case EVENT_WIFI_RADIO_STATE_CHANGE:
+ case EVENT_NETWORK_STATE_CHANGE:
+ case EVENT_SUPPLICANT_STATE_CHANGE:
+ case EVENT_BSSID_CHANGE:
+ case CMD_RSSI_FETCH:
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ // ignore
+ break;
+ case EVENT_SCREEN_ON:
+ mIsScreenOn = true;
+ break;
+ case EVENT_SCREEN_OFF:
+ mIsScreenOn = false;
+ break;
+ default:
+ loge("Unhandled message " + msg + " in state " + getCurrentState().getName());
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi watchdog is disabled by the setting.
+ */
+ class WatchdogDisabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (isWatchdogEnabled())
+ transitionTo(mNotConnectedState);
+ return HANDLED;
+ case EVENT_NETWORK_STATE_CHANGE:
+ Intent intent = (Intent) msg.obj;
+ NetworkInfo networkInfo = (NetworkInfo)
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+
+ switch (networkInfo.getDetailedState()) {
+ case VERIFYING_POOR_LINK:
+ if (DBG) logd("Watchdog disabled, verify link");
+ sendLinkStatusNotification(true);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /**
+ * WiFi watchdog is enabled by the setting.
+ */
+ class WatchdogEnabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Intent intent;
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (!isWatchdogEnabled())
+ transitionTo(mWatchdogDisabledState);
+ break;
+
+ case EVENT_NETWORK_STATE_CHANGE:
+ intent = (Intent) msg.obj;
+ NetworkInfo networkInfo =
+ (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ if (DBG) logd("Network state change " + networkInfo.getDetailedState());
+
+ mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+ updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null);
+
+ switch (networkInfo.getDetailedState()) {
+ case VERIFYING_POOR_LINK:
+ mLinkProperties = (LinkProperties) intent.getParcelableExtra(
+ WifiManager.EXTRA_LINK_PROPERTIES);
+ if (mPoorNetworkDetectionEnabled) {
+ if (mWifiInfo == null || mCurrentBssid == null) {
+ loge("Ignore, wifiinfo " + mWifiInfo +" bssid " + mCurrentBssid);
+ sendLinkStatusNotification(true);
+ } else {
+ transitionTo(mVerifyingLinkState);
+ }
+ } else {
+ sendLinkStatusNotification(true);
+ }
+ break;
+ case CONNECTED:
+ transitionTo(mOnlineWatchState);
+ break;
+ default:
+ transitionTo(mNotConnectedState);
+ break;
+ }
+ break;
+
+ case EVENT_SUPPLICANT_STATE_CHANGE:
+ intent = (Intent) msg.obj;
+ SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra(
+ WifiManager.EXTRA_NEW_STATE);
+ if (supplicantState == SupplicantState.COMPLETED) {
+ mWifiInfo = mWifiManager.getConnectionInfo();
+ updateCurrentBssid(mWifiInfo.getBSSID());
+ }
+ break;
+
+ case EVENT_WIFI_RADIO_STATE_CHANGE:
+ if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) {
+ transitionTo(mNotConnectedState);
+ }
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi is disconnected.
+ */
+ class NotConnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+ }
+
+ /**
+ * WiFi is connected, but waiting for good link detection message.
+ */
+ class VerifyingLinkState extends State {
+
+ private int mSampleCount;
+
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSampleCount = 0;
+ mCurrentBssid.newLinkDetected();
+ sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (!mPoorNetworkDetectionEnabled) {
+ sendLinkStatusNotification(true);
+ }
+ break;
+
+ case EVENT_BSSID_CHANGE:
+ transitionTo(mVerifyingLinkState);
+ break;
+
+ case CMD_RSSI_FETCH:
+ if (msg.arg1 == mRssiFetchToken) {
+ mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
+ sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
+ LINK_SAMPLING_INTERVAL_MS);
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
+ int rssi = info.rssi;
+ if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi);
+
+ long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime();
+ if (time <= 0) {
+ // max avoidance time is met
+ if (DBG) logd("Max avoid time elapsed");
+ sendLinkStatusNotification(true);
+ } else {
+ if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) {
+ if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) {
+ // link is good again
+ if (DBG) logd("Good link detected, rssi=" + rssi);
+ mCurrentBssid.mBssidAvoidTimeMax = 0;
+ sendLinkStatusNotification(true);
+ }
+ } else {
+ mSampleCount = 0;
+ if (DBG) logd("Link is still poor, time left=" + time);
+ }
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ if (DBG) logd("RSSI_FETCH_FAILED");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi is connected and link is verified.
+ */
+ class ConnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (mPoorNetworkDetectionEnabled) {
+ transitionTo(mOnlineWatchState);
+ } else {
+ transitionTo(mOnlineState);
+ }
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /**
+ * RSSI is high enough and don't need link monitoring.
+ */
+ class OnlineWatchState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ if (mPoorNetworkDetectionEnabled) {
+ // treat entry as an rssi change
+ handleRssiChange();
+ } else {
+ transitionTo(mOnlineState);
+ }
+ }
+
+ private void handleRssiChange() {
+ if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD && mCurrentBssid != null) {
+ transitionTo(mLinkMonitoringState);
+ } else {
+ // stay here
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ handleRssiChange();
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * Keep sampling the link and monitor any poor link situation.
+ */
+ class LinkMonitoringState extends State {
+
+ private int mSampleCount;
+
+ private int mLastRssi;
+ private int mLastTxGood;
+ private int mLastTxBad;
+
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSampleCount = 0;
+ mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR);
+ sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) {
+ // stay here;
+ } else {
+ // we don't need frequent RSSI monitoring any more
+ transitionTo(mOnlineWatchState);
+ }
+ break;
+
+ case EVENT_BSSID_CHANGE:
+ transitionTo(mLinkMonitoringState);
+ break;
+
+ case CMD_RSSI_FETCH:
+ if (!mIsScreenOn) {
+ transitionTo(mOnlineState);
+ } else if (msg.arg1 == mRssiFetchToken) {
+ mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
+ sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
+ LINK_SAMPLING_INTERVAL_MS);
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
+ int rssi = info.rssi;
+ int mrssi = (mLastRssi + rssi) / 2;
+ int txbad = info.txbad;
+ int txgood = info.txgood;
+ if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad="
+ + txbad + " txgood=" + txgood);
+
+ // skip the first data point as we want incremental values
+ long now = SystemClock.elapsedRealtime();
+ if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) {
+
+ // update packet loss statistics
+ int dbad = txbad - mLastTxBad;
+ int dgood = txgood - mLastTxGood;
+ int dtotal = dbad + dgood;
+
+ if (dtotal > 0) {
+ // calculate packet loss in the last sampling interval
+ double loss = ((double) dbad) / ((double) dtotal);
+
+ mCurrentLoss.update(loss, dtotal);
+
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss="
+ + df.format(mCurrentLoss.mValue * 100) + "% volume="
+ + df.format(mCurrentLoss.mVolume));
+ }
+
+ mCurrentBssid.updateLoss(mrssi, loss, dtotal);
+
+ // check for high packet loss and send poor link notification
+ if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD
+ && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) {
+ if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT)
+ if (mCurrentBssid.poorLinkDetected(rssi)) {
+ sendLinkStatusNotification(false);
+ ++mRssiFetchToken;
+ }
+ } else {
+ mSampleCount = 0;
+ }
+ }
+ }
+
+ mCurrentBssid.mLastTimeSample = now;
+ mLastTxBad = txbad;
+ mLastTxGood = txgood;
+ mLastRssi = rssi;
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ // can happen if we are waiting to get a disconnect notification
+ if (DBG) logd("RSSI_FETCH_FAILED");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * Child state of ConnectedState indicating that we are online and there is nothing to do.
+ */
+ class OnlineState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_SCREEN_ON:
+ mIsScreenOn = true;
+ if (mPoorNetworkDetectionEnabled)
+ transitionTo(mOnlineWatchState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private void updateCurrentBssid(String bssid) {
+ if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));
+
+ // if currently not connected, then set current BSSID to null
+ if (bssid == null) {
+ if (mCurrentBssid == null) return;
+ mCurrentBssid = null;
+ if (DBG) logd("BSSID changed");
+ sendMessage(EVENT_BSSID_CHANGE);
+ return;
+ }
+
+ // if it is already the current BSSID, then done
+ if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return;
+
+ // search for the new BSSID in the cache, add to cache if not found
+ mCurrentBssid = mBssidCache.get(bssid);
+ if (mCurrentBssid == null) {
+ mCurrentBssid = new BssidStatistics(bssid);
+ mBssidCache.put(bssid, mCurrentBssid);
+ }
+
+ // send BSSID change notification
+ if (DBG) logd("BSSID changed");
+ sendMessage(EVENT_BSSID_CHANGE);
+ }
+
+ private int calculateSignalLevel(int rssi) {
+ int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
+ if (DBG)
+ logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel);
+ return signalLevel;
+ }
+
+ private void sendLinkStatusNotification(boolean isGood) {
+ if (DBG) logd("########################################");
+ if (isGood) {
+ mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+ if (mCurrentBssid != null) {
+ mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime();
+ }
+ if (DBG) logd("Good link notification is sent");
+ } else {
+ mWsmChannel.sendMessage(POOR_LINK_DETECTED);
+ if (mCurrentBssid != null) {
+ mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime();
+ }
+ logd("Poor link notification is sent");
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a
+ * boolean. Note that internally setting values are always stored as
+ * strings; this function converts the string to a boolean for you. The
+ * default value will be returned if the setting is not defined or not a
+ * valid boolean.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not
+ * a valid boolean.
+ */
+ private static boolean getSettingsGlobalBoolean(ContentResolver cr, String name, boolean def) {
+ return Settings.Global.getInt(cr, name, def ? 1 : 0) == 1;
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an integer.
+ * This will either create a new entry in the table if the given name does
+ * not exist, or modify the value of the existing row with that name. Note
+ * that internally setting values are always stored as strings, so this
+ * function converts the given value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ private static boolean putSettingsGlobalBoolean(ContentResolver cr, String name, boolean value) {
+ return Settings.Global.putInt(cr, name, value ? 1 : 0);
+ }
+
+ /**
+ * Bundle of good link count parameters
+ */
+ private static class GoodLinkTarget {
+ public final int RSSI_ADJ_DBM;
+ public final int SAMPLE_COUNT;
+ public final int REDUCE_TIME_MS;
+ public GoodLinkTarget(int adj, int count, int time) {
+ RSSI_ADJ_DBM = adj;
+ SAMPLE_COUNT = count;
+ REDUCE_TIME_MS = time;
+ }
+ }
+
+ /**
+ * Bundle of max avoidance time parameters
+ */
+ private static class MaxAvoidTime {
+ public final int TIME_MS;
+ public final int MIN_RSSI_DBM;
+ public MaxAvoidTime(int time, int rssi) {
+ TIME_MS = time;
+ MIN_RSSI_DBM = rssi;
+ }
+ }
+
+ /**
+ * Volume-weighted Exponential Moving Average (V-EMA)
+ * - volume-weighted: each update has its own weight (number of packets)
+ * - exponential: O(1) time and O(1) space for both update and query
+ * - moving average: reflect most recent results and expire old ones
+ */
+ private class VolumeWeightedEMA {
+ private double mValue;
+ private double mVolume;
+ private double mProduct;
+ private final double mAlpha;
+
+ public VolumeWeightedEMA(double coefficient) {
+ mValue = 0.0;
+ mVolume = 0.0;
+ mProduct = 0.0;
+ mAlpha = coefficient;
+ }
+
+ public void update(double newValue, int newVolume) {
+ if (newVolume <= 0) return;
+ // core update formulas
+ double newProduct = newValue * newVolume;
+ mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct;
+ mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume;
+ mValue = mProduct / mVolume;
+ }
+ }
+
+ /**
+ * Record (RSSI -> pakce loss %) mappings of one BSSID
+ */
+ private class BssidStatistics {
+
+ /* MAC address of this BSSID */
+ private final String mBssid;
+
+ /* RSSI -> packet loss % mappings */
+ private VolumeWeightedEMA[] mEntries;
+ private int mRssiBase;
+ private int mEntriesSize;
+
+ /* Target to send good link notification, set when poor link is detected */
+ private int mGoodLinkTargetRssi;
+ private int mGoodLinkTargetCount;
+
+ /* Index of GOOD_LINK_TARGET array */
+ private int mGoodLinkTargetIndex;
+
+ /* Timestamps of some last events */
+ private long mLastTimeSample;
+ private long mLastTimeGood;
+ private long mLastTimePoor;
+
+ /* Max time to avoid this BSSID */
+ private long mBssidAvoidTimeMax;
+
+ /**
+ * Constructor
+ *
+ * @param bssid is the address of this BSSID
+ */
+ public BssidStatistics(String bssid) {
+ this.mBssid = bssid;
+ mRssiBase = BSSID_STAT_RANGE_LOW_DBM;
+ mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1;
+ mEntries = new VolumeWeightedEMA[mEntriesSize];
+ for (int i = 0; i < mEntriesSize; i++)
+ mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD);
+ }
+
+ /**
+ * Update this BSSID cache
+ *
+ * @param rssi is the RSSI
+ * @param value is the new instant loss value at this RSSI
+ * @param volume is the volume for this single update
+ */
+ public void updateLoss(int rssi, double value, int volume) {
+ if (volume <= 0) return;
+ int index = rssi - mRssiBase;
+ if (index < 0 || index >= mEntriesSize) return;
+ mEntries[index].update(value, volume);
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100)
+ + "% volume=" + df.format(mEntries[index].mVolume));
+ }
+ }
+
+ /**
+ * Get preset loss if the cache has insufficient data, observed from experiments.
+ *
+ * @param rssi is the input RSSI
+ * @return preset loss of the given RSSI
+ */
+ public double presetLoss(int rssi) {
+ if (rssi <= -90) return 1.0;
+ if (rssi > 0) return 0.0;
+
+ if (sPresetLoss == null) {
+ // pre-calculate all preset losses only once, then reuse them
+ final int size = 90;
+ sPresetLoss = new double[size];
+ for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5);
+ }
+ return sPresetLoss[-rssi];
+ }
+
+ /**
+ * A poor link is detected, calculate a target RSSI to bring WiFi back.
+ *
+ * @param rssi is the current RSSI
+ * @return true iff the current BSSID should be avoided
+ */
+ public boolean poorLinkDetected(int rssi) {
+ if (DBG) logd("Poor link detected, rssi=" + rssi);
+
+ long now = SystemClock.elapsedRealtime();
+ long lastGood = now - mLastTimeGood;
+ long lastPoor = now - mLastTimePoor;
+
+ // reduce the difficulty of good link target if last avoidance was long time ago
+ while (mGoodLinkTargetIndex > 0
+ && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS)
+ mGoodLinkTargetIndex--;
+ mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT;
+
+ // scan for a target RSSI at which the link is good
+ int from = rssi + GOOD_LINK_RSSI_RANGE_MIN;
+ int to = rssi + GOOD_LINK_RSSI_RANGE_MAX;
+ mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
+ mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM;
+ if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++;
+
+ // calculate max avoidance time to prevent avoiding forever
+ int p = 0, pmax = MAX_AVOID_TIME.length - 1;
+ while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++;
+ long avoidMax = MAX_AVOID_TIME[p].TIME_MS;
+
+ // don't avoid if max avoidance time is 0 (RSSI is super high)
+ if (avoidMax <= 0) return false;
+
+ // set max avoidance time, send poor link notification
+ mBssidAvoidTimeMax = now + avoidMax;
+
+ if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount
+ + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax);
+
+ return true;
+ }
+
+ /**
+ * A new BSSID is connected, recalculate target RSSI threshold
+ */
+ public void newLinkDetected() {
+ // if this BSSID is currently being avoided, the reuse those values
+ if (mBssidAvoidTimeMax > 0) {
+ if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi
+ + " count=" + mGoodLinkTargetCount);
+ return;
+ }
+
+ // calculate a new RSSI threshold for new link verifying
+ int from = BSSID_STAT_RANGE_LOW_DBM;
+ int to = BSSID_STAT_RANGE_HIGH_DBM;
+ mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
+ mGoodLinkTargetCount = 1;
+ mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS;
+ if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count="
+ + mGoodLinkTargetCount);
+ }
+
+ /**
+ * Return the first RSSI within the range where loss[rssi] < threshold
+ *
+ * @param from start scanning from this RSSI
+ * @param to stop scanning at this RSSI
+ * @param threshold target threshold for scanning
+ * @return target RSSI
+ */
+ public int findRssiTarget(int from, int to, double threshold) {
+ from -= mRssiBase;
+ to -= mRssiBase;
+ int emptyCount = 0;
+ int d = from < to ? 1 : -1;
+ for (int i = from; i != to; i += d)
+ // don't use a data point if it volume is too small (statistically unreliable)
+ if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) {
+ emptyCount = 0;
+ if (mEntries[i].mValue < threshold) {
+ // scan target found
+ int rssi = mRssiBase + i;
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Scan target found: rssi=" + rssi + " threshold="
+ + df.format(threshold * 100) + "% value="
+ + df.format(mEntries[i].mValue * 100) + "% volume="
+ + df.format(mEntries[i].mVolume));
+ }
+ return rssi;
+ }
+ } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) {
+ // cache has insufficient data around this RSSI, use preset loss instead
+ int rssi = mRssiBase + i;
+ double lossPreset = presetLoss(rssi);
+ if (lossPreset < threshold) {
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Scan target found: rssi=" + rssi + " threshold="
+ + df.format(threshold * 100) + "% value="
+ + df.format(lossPreset * 100) + "% volume=preset");
+ }
+ return rssi;
+ }
+ }
+
+ return mRssiBase + to;
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pService.java b/service/java/com/android/server/wifi/p2p/WifiP2pService.java
new file mode 100644
index 000000000..c5ad51775
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.server.wifi.p2p;
+
+import android.content.Context;
+import android.util.Log;
+import com.android.server.SystemService;
+
+public final class WifiP2pService extends SystemService {
+
+ private static final String TAG = "WifiService";
+ WifiP2pServiceImpl mImpl;
+
+ public WifiP2pService() { }
+
+ @Override
+ public void onCreate(Context context) {
+ mImpl = new WifiP2pServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "Registering " + Context.WIFI_SERVICE);
+ publishBinderService(Context.WIFI_P2P_SERVICE, mImpl);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mImpl.connectivityServiceReady();
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
new file mode 100644
index 000000000..c08d09d9b
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -0,0 +1,2960 @@
+/*
+ * Copyright (C) 2011 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.server.wifi.p2p;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.DhcpStateMachine;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkInfo;
+import android.net.NetworkUtils;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.IWifiP2pManager;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pGroupList;
+import android.net.wifi.p2p.WifiP2pGroupList.GroupDeleteListener;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.SystemService;
+import com.android.server.wifi.WifiMonitor;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiStateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+
+/**
+ * WifiP2pService includes a state machine to perform Wi-Fi p2p operations. Applications
+ * communicate with this service to issue device discovery and connectivity requests
+ * through the WifiP2pManager interface. The state machine communicates with the wifi
+ * driver through wpa_supplicant and handles the event responses through WifiMonitor.
+ *
+ * Note that the term Wifi when used without a p2p suffix refers to the client mode
+ * of Wifi operation
+ * @hide
+ */
+public final class WifiP2pServiceImpl extends IWifiP2pManager.Stub {
+ private static final String TAG = "WifiP2pService";
+ private static final boolean DBG = false;
+ private static final String NETWORKTYPE = "WIFI_P2P";
+
+ private Context mContext;
+ private String mInterface;
+ private Notification mNotification;
+
+ INetworkManagementService mNwService;
+ private DhcpStateMachine mDhcpStateMachine;
+
+ private P2pStateMachine mP2pStateMachine;
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+ private AsyncChannel mWifiChannel;
+
+ private static final Boolean JOIN_GROUP = true;
+ private static final Boolean FORM_GROUP = false;
+
+ private static final Boolean RELOAD = true;
+ private static final Boolean NO_RELOAD = false;
+
+ /* Two minutes comes from the wpa_supplicant setting */
+ private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
+ private static int mGroupCreatingTimeoutIndex = 0;
+
+ private static final int DISABLE_P2P_WAIT_TIME_MS = 5 * 1000;
+ private static int mDisableP2pTimeoutIndex = 0;
+
+ /* Set a two minute discover timeout to avoid STA scans from being blocked */
+ private static final int DISCOVER_TIMEOUT_S = 120;
+
+ /* Idle time after a peer is gone when the group is torn down */
+ private static final int GROUP_IDLE_TIME_S = 10;
+
+ private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE;
+
+ /* Delayed message to timeout group creation */
+ public static final int GROUP_CREATING_TIMED_OUT = BASE + 1;
+
+ /* User accepted a peer request */
+ private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 2;
+ /* User rejected a peer request */
+ private static final int PEER_CONNECTION_USER_REJECT = BASE + 3;
+ /* User wants to disconnect wifi in favour of p2p */
+ private static final int DROP_WIFI_USER_ACCEPT = BASE + 4;
+ /* User wants to keep his wifi connection and drop p2p */
+ private static final int DROP_WIFI_USER_REJECT = BASE + 5;
+ /* Delayed message to timeout p2p disable */
+ public static final int DISABLE_P2P_TIMED_OUT = BASE + 6;
+
+
+ /* Commands to the WifiStateMachine */
+ public static final int P2P_CONNECTION_CHANGED = BASE + 11;
+
+ /* These commands are used to temporarily disconnect wifi when we detect
+ * a frequency conflict which would make it impossible to have with p2p
+ * and wifi active at the same time.
+ *
+ * If the user chooses to disable wifi temporarily, we keep wifi disconnected
+ * until the p2p connection is done and terminated at which point we will
+ * bring back wifi up
+ *
+ * DISCONNECT_WIFI_REQUEST
+ * msg.arg1 = 1 enables temporary disconnect and 0 disables it.
+ */
+ public static final int DISCONNECT_WIFI_REQUEST = BASE + 12;
+ public static final int DISCONNECT_WIFI_RESPONSE = BASE + 13;
+
+ public static final int SET_MIRACAST_MODE = BASE + 14;
+
+ // During dhcp (and perhaps other times) we can't afford to drop packets
+ // but Discovery will switch our channel enough we will.
+ // msg.arg1 = ENABLED for blocking, DISABLED for resumed.
+ // msg.arg2 = msg to send when blocked
+ // msg.obj = StateMachine to send to when blocked
+ public static final int BLOCK_DISCOVERY = BASE + 15;
+
+ // set country code
+ public static final int SET_COUNTRY_CODE = BASE + 16;
+
+ public static final int ENABLED = 1;
+ public static final int DISABLED = 0;
+
+ private final boolean mP2pSupported;
+
+ private WifiP2pDevice mThisDevice = new WifiP2pDevice();
+
+ /* When a group has been explicitly created by an app, we persist the group
+ * even after all clients have been disconnected until an explicit remove
+ * is invoked */
+ private boolean mAutonomousGroup;
+
+ /* Invitation to join an existing p2p group */
+ private boolean mJoinExistingGroup;
+
+ /* Track whether we are in p2p discovery. This is used to avoid sending duplicate
+ * broadcasts
+ */
+ private boolean mDiscoveryStarted;
+ /* Track whether servcice/peer discovery is blocked in favor of other wifi actions
+ * (notably dhcp)
+ */
+ private boolean mDiscoveryBlocked;
+
+ // Supplicant doesn't like setting the same country code multiple times (it may drop
+ // current connected network), so we save the country code here to avoid redundency
+ private String mLastSetCountryCode;
+
+ /*
+ * remember if we were in a scan when it had to be stopped
+ */
+ private boolean mDiscoveryPostponed = false;
+
+ private NetworkInfo mNetworkInfo;
+
+ private boolean mTempoarilyDisconnectedWifi = false;
+
+ /* The transaction Id of service discovery request */
+ private byte mServiceTransactionId = 0;
+
+ /* Service discovery request ID of wpa_supplicant.
+ * null means it's not set yet. */
+ private String mServiceDiscReqId;
+
+ /* clients(application) information list. */
+ private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
+
+ /* Is chosen as a unique range to avoid conflict with
+ the range defined in Tethering.java */
+ private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
+ private static final String SERVER_ADDRESS = "192.168.49.1";
+
+ /**
+ * Error code definition.
+ * see the Table.8 in the WiFi Direct specification for the detail.
+ */
+ public static enum P2pStatus {
+ /* Success. */
+ SUCCESS,
+
+ /* The target device is currently unavailable. */
+ INFORMATION_IS_CURRENTLY_UNAVAILABLE,
+
+ /* Protocol error. */
+ INCOMPATIBLE_PARAMETERS,
+
+ /* The target device reached the limit of the number of the connectable device.
+ * For example, device limit or group limit is set. */
+ LIMIT_REACHED,
+
+ /* Protocol error. */
+ INVALID_PARAMETER,
+
+ /* Unable to accommodate request. */
+ UNABLE_TO_ACCOMMODATE_REQUEST,
+
+ /* Previous protocol error, or disruptive behavior. */
+ PREVIOUS_PROTOCOL_ERROR,
+
+ /* There is no common channels the both devices can use. */
+ NO_COMMON_CHANNEL,
+
+ /* Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
+ * but device B has removed the specified credential already. */
+ UNKNOWN_P2P_GROUP,
+
+ /* Both p2p devices indicated an intent of 15 in group owner negotiation. */
+ BOTH_GO_INTENT_15,
+
+ /* Incompatible provisioning method. */
+ INCOMPATIBLE_PROVISIONING_METHOD,
+
+ /* Rejected by user */
+ REJECTED_BY_USER,
+
+ /* Unknown error */
+ UNKNOWN;
+
+ public static P2pStatus valueOf(int error) {
+ switch(error) {
+ case 0 :
+ return SUCCESS;
+ case 1:
+ return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
+ case 2:
+ return INCOMPATIBLE_PARAMETERS;
+ case 3:
+ return LIMIT_REACHED;
+ case 4:
+ return INVALID_PARAMETER;
+ case 5:
+ return UNABLE_TO_ACCOMMODATE_REQUEST;
+ case 6:
+ return PREVIOUS_PROTOCOL_ERROR;
+ case 7:
+ return NO_COMMON_CHANNEL;
+ case 8:
+ return UNKNOWN_P2P_GROUP;
+ case 9:
+ return BOTH_GO_INTENT_15;
+ case 10:
+ return INCOMPATIBLE_PROVISIONING_METHOD;
+ case 11:
+ return REJECTED_BY_USER;
+ default:
+ return UNKNOWN;
+ }
+ }
+ }
+
+ public WifiP2pServiceImpl(Context context) {
+ mContext = context;
+
+ //STOPSHIP: get this from native side
+ mInterface = "p2p0";
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
+
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
+ mThisDevice.primaryDeviceType = mContext.getResources().getString(
+ com.android.internal.R.string.config_wifi_p2p_device_type);
+
+ mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
+ mP2pStateMachine.start();
+ }
+
+ public void connectivityServiceReady() {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNwService = INetworkManagementService.Stub.asInterface(b);
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+ "WifiP2pService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+ "WifiP2pService");
+ }
+
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "WifiP2pService");
+ }
+
+ /**
+ * Get a reference to handler. This is used by a client to establish
+ * an AsyncChannel communication with WifiP2pService
+ */
+ public Messenger getMessenger() {
+ enforceAccessPermission();
+ enforceChangePermission();
+ return new Messenger(mP2pStateMachine.getHandler());
+ }
+
+ /** This is used to provide information to drivers to optimize performance depending
+ * on the current mode of operation.
+ * 0 - disabled
+ * 1 - source operation
+ * 2 - sink operation
+ *
+ * As an example, the driver could reduce the channel dwell time during scanning
+ * when acting as a source or sink to minimize impact on miracast.
+ */
+ public void setMiracastMode(int mode) {
+ enforceConnectivityInternalPermission();
+ mP2pStateMachine.sendMessage(SET_MIRACAST_MODE, mode);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump WifiP2pService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ mP2pStateMachine.dump(fd, pw, args);
+ pw.println("mAutonomousGroup " + mAutonomousGroup);
+ pw.println("mJoinExistingGroup " + mJoinExistingGroup);
+ pw.println("mDiscoveryStarted " + mDiscoveryStarted);
+ pw.println("mNetworkInfo " + mNetworkInfo);
+ pw.println("mTempoarilyDisconnectedWifi " + mTempoarilyDisconnectedWifi);
+ pw.println("mServiceDiscReqId " + mServiceDiscReqId);
+ pw.println();
+ }
+
+
+ /**
+ * Handles interaction with WifiStateMachine
+ */
+ private class P2pStateMachine extends StateMachine {
+
+ private DefaultState mDefaultState = new DefaultState();
+ private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState();
+ private P2pDisablingState mP2pDisablingState = new P2pDisablingState();
+ private P2pDisabledState mP2pDisabledState = new P2pDisabledState();
+ private P2pEnablingState mP2pEnablingState = new P2pEnablingState();
+ private P2pEnabledState mP2pEnabledState = new P2pEnabledState();
+ // Inactive is when p2p is enabled with no connectivity
+ private InactiveState mInactiveState = new InactiveState();
+ private GroupCreatingState mGroupCreatingState = new GroupCreatingState();
+ private UserAuthorizingInviteRequestState mUserAuthorizingInviteRequestState
+ = new UserAuthorizingInviteRequestState();
+ private UserAuthorizingNegotiationRequestState mUserAuthorizingNegotiationRequestState
+ = new UserAuthorizingNegotiationRequestState();
+ private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState();
+ private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
+ private FrequencyConflictState mFrequencyConflictState =new FrequencyConflictState();
+
+ private GroupCreatedState mGroupCreatedState = new GroupCreatedState();
+ private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState();
+ private OngoingGroupRemovalState mOngoingGroupRemovalState = new OngoingGroupRemovalState();
+
+ private WifiNative mWifiNative = new WifiNative(mInterface);
+ private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
+
+ private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
+ /* During a connection, supplicant can tell us that a device was lost. From a supplicant's
+ * perspective, the discovery stops during connection and it purges device since it does
+ * not get latest updates about the device without being in discovery state.
+ *
+ * From the framework perspective, the device is still there since we are connecting or
+ * connected to it. so we keep these devices in a separate list, so that they are removed
+ * when connection is cancelled or lost
+ */
+ private final WifiP2pDeviceList mPeersLostDuringConnection = new WifiP2pDeviceList();
+ private final WifiP2pGroupList mGroups = new WifiP2pGroupList(null,
+ new GroupDeleteListener() {
+ @Override
+ public void onDeleteGroup(int netId) {
+ if (DBG) logd("called onDeleteGroup() netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ mWifiNative.saveConfig();
+ sendP2pPersistentGroupsChangedBroadcast();
+ }
+ });
+ private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
+ private WifiP2pGroup mGroup;
+
+ // Saved WifiP2pConfig for an ongoing peer connection. This will never be null.
+ // The deviceAddress will be an empty string when the device is inactive
+ // or if it is connected without any ongoing join request
+ private WifiP2pConfig mSavedPeerConfig = new WifiP2pConfig();
+
+ // Saved WifiP2pGroup from invitation request
+ private WifiP2pGroup mSavedP2pGroup;
+
+ P2pStateMachine(String name, boolean p2pSupported) {
+ super(name);
+
+ addState(mDefaultState);
+ addState(mP2pNotSupportedState, mDefaultState);
+ addState(mP2pDisablingState, mDefaultState);
+ addState(mP2pDisabledState, mDefaultState);
+ addState(mP2pEnablingState, mDefaultState);
+ addState(mP2pEnabledState, mDefaultState);
+ addState(mInactiveState, mP2pEnabledState);
+ addState(mGroupCreatingState, mP2pEnabledState);
+ addState(mUserAuthorizingInviteRequestState, mGroupCreatingState);
+ addState(mUserAuthorizingNegotiationRequestState, mGroupCreatingState);
+ addState(mProvisionDiscoveryState, mGroupCreatingState);
+ addState(mGroupNegotiationState, mGroupCreatingState);
+ addState(mFrequencyConflictState, mGroupCreatingState);
+ addState(mGroupCreatedState, mP2pEnabledState);
+ addState(mUserAuthorizingJoinState, mGroupCreatedState);
+ addState(mOngoingGroupRemovalState, mGroupCreatedState);
+
+ if (p2pSupported) {
+ setInitialState(mP2pDisabledState);
+ } else {
+ setInitialState(mP2pNotSupportedState);
+ }
+ setLogRecSize(50);
+ setLogOnlyTransitions(true);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (DBG) logd("Full connection with WifiStateMachine established");
+ mWifiChannel = (AsyncChannel) message.obj;
+ } else {
+ loge("Full connection failure, error = " + message.arg1);
+ mWifiChannel = null;
+ }
+ break;
+
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+ loge("Send failed, client connection lost");
+ } else {
+ loge("Client connection lost with reason: " + message.arg1);
+ }
+ mWifiChannel = null;
+ break;
+
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, getHandler(), message.replyTo);
+ break;
+ case BLOCK_DISCOVERY:
+ mDiscoveryBlocked = (message.arg1 == ENABLED ? true : false);
+ // always reset this - we went to a state that doesn't support discovery so
+ // it would have stopped regardless
+ mDiscoveryPostponed = false;
+ if (mDiscoveryBlocked) {
+ try {
+ StateMachine m = (StateMachine)message.obj;
+ m.sendMessage(message.arg2);
+ } catch (Exception e) {
+ loge("unable to send BLOCK_DISCOVERY response: " + e);
+ }
+ }
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CONNECT:
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REQUEST_PEERS:
+ replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
+ new WifiP2pDeviceList(mPeers));
+ break;
+ case WifiP2pManager.REQUEST_CONNECTION_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
+ new WifiP2pInfo(mWifiP2pInfo));
+ break;
+ case WifiP2pManager.REQUEST_GROUP_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
+ mGroup != null ? new WifiP2pGroup(mGroup) : null);
+ break;
+ case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
+ new WifiP2pGroupList(mGroups, null));
+ break;
+ case WifiP2pManager.START_WPS:
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ // Ignore
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.WPS_SUCCESS_EVENT:
+ case WifiMonitor.WPS_FAIL_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case WifiMonitor.WPS_TIMEOUT_EVENT:
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ case WifiMonitor.P2P_FIND_STOPPED_EVENT:
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ case PEER_CONNECTION_USER_ACCEPT:
+ case PEER_CONNECTION_USER_REJECT:
+ case DISCONNECT_WIFI_RESPONSE:
+ case DROP_WIFI_USER_ACCEPT:
+ case DROP_WIFI_USER_REJECT:
+ case GROUP_CREATING_TIMED_OUT:
+ case DISABLE_P2P_TIMED_OUT:
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ case DhcpStateMachine.CMD_ON_QUIT:
+ case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+ case SET_MIRACAST_MODE:
+ case WifiP2pManager.START_LISTEN:
+ case WifiP2pManager.STOP_LISTEN:
+ case WifiP2pManager.SET_CHANNEL:
+ case SET_COUNTRY_CODE:
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ // Enable is lazy and has no response
+ break;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ // If we end up handling in default, p2p is not enabled
+ mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+ break;
+ /* unexpected group created, remove */
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ loge("Unexpected group creation, remove " + mGroup);
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ break;
+ // A group formation failure is always followed by
+ // a group removed event. Flushing things at group formation
+ // failure causes supplicant issues. Ignore right now.
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ break;
+ default:
+ loge("Unhandled message " + message);
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pNotSupportedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiP2pManager.DISCOVER_PEERS:
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CONNECT:
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.START_WPS:
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.START_LISTEN:
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pDisablingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
+ ++mDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (DBG) logd("p2p socket connection lost");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ deferMessage(message);
+ break;
+ case DISABLE_P2P_TIMED_OUT:
+ if (mGroupCreatingTimeoutIndex == message.arg1) {
+ loge("P2p disable timed out");
+ transitionTo(mP2pDisabledState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+ }
+ }
+
+ class P2pDisabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ try {
+ mNwService.setInterfaceUp(mInterface);
+ } catch (RemoteException re) {
+ loge("Unable to change interface settings: " + re);
+ } catch (IllegalStateException ie) {
+ loge("Unable to change interface settings: " + ie);
+ }
+ mWifiMonitor.startMonitoring();
+ transitionTo(mP2pEnablingState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pEnablingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ if (DBG) logd("P2p socket connection successful");
+ transitionTo(mInactiveState);
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ loge("P2p socket connection failed");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pEnabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendP2pStateChangedBroadcast(true);
+ mNetworkInfo.setIsAvailable(true);
+ sendP2pConnectionChangedBroadcast();
+ initializeP2pSettings();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ loge("Unexpected loss of p2p socket connection");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ //Nothing to do
+ break;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ if (mPeers.clear()) {
+ sendPeersChangedBroadcast();
+ }
+ if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
+
+ mWifiMonitor.stopMonitoring();
+ transitionTo(mP2pDisablingState);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ {
+ WifiP2pDevice d = (WifiP2pDevice) message.obj;
+ if (d != null && setAndPersistDeviceName(d.deviceName)) {
+ if (DBG) logd("set device name " + d.deviceName);
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ }
+ case WifiP2pManager.SET_WFD_INFO:
+ {
+ WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
+ if (d != null && setWfdInfo(d)) {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ }
+ case BLOCK_DISCOVERY:
+ boolean blocked = (message.arg1 == ENABLED ? true : false);
+ if (mDiscoveryBlocked == blocked) break;
+ mDiscoveryBlocked = blocked;
+ if (blocked && mDiscoveryStarted) {
+ mWifiNative.p2pStopFind();
+ mDiscoveryPostponed = true;
+ }
+ if (!blocked && mDiscoveryPostponed) {
+ mDiscoveryPostponed = false;
+ mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
+ }
+ if (blocked) {
+ try {
+ StateMachine m = (StateMachine)message.obj;
+ m.sendMessage(message.arg2);
+ } catch (Exception e) {
+ loge("unable to send BLOCK_DISCOVERY response: " + e);
+ }
+ }
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ if (mDiscoveryBlocked) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ }
+ // do not send service discovery request while normal find operation.
+ clearSupplicantServiceRequest();
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
+ sendP2pDiscoveryChangedBroadcast(true);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_FIND_STOPPED_EVENT:
+ sendP2pDiscoveryChangedBroadcast(false);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ if (mDiscoveryBlocked) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ }
+ if (DBG) logd(getName() + " discover services");
+ if (!updateSupplicantServiceRequest()) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.NO_SERVICE_REQUESTS);
+ break;
+ }
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
+ mPeers.updateSupplicantDetails(device);
+ sendPeersChangedBroadcast();
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ // Gets current details for the one removed
+ device = mPeers.remove(device.deviceAddress);
+ if (device != null) {
+ sendPeersChangedBroadcast();
+ }
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " add service");
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
+ if (addLocalService(message.replyTo, servInfo)) {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+ }
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " remove service");
+ servInfo = (WifiP2pServiceInfo)message.obj;
+ removeLocalService(message.replyTo, servInfo);
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ if (DBG) logd(getName() + " clear service");
+ clearLocalServices(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " add service request");
+ if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+ break;
+ }
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " remove service request");
+ removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
+ replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ if (DBG) logd(getName() + " clear service request");
+ clearServiceRequests(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+ break;
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ if (DBG) logd(getName() + " receive service response");
+ List<WifiP2pServiceResponse> sdRespList =
+ (List<WifiP2pServiceResponse>) message.obj;
+ for (WifiP2pServiceResponse resp : sdRespList) {
+ WifiP2pDevice dev =
+ mPeers.get(resp.getSrcDevice().deviceAddress);
+ resp.setSrcDevice(dev);
+ sendServiceResponse(resp);
+ }
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ if (DBG) logd(getName() + " delete persistent group");
+ mGroups.remove(message.arg1);
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
+ break;
+ case SET_MIRACAST_MODE:
+ mWifiNative.setMiracastMode(message.arg1);
+ break;
+ case WifiP2pManager.START_LISTEN:
+ if (DBG) logd(getName() + " start listen mode");
+ mWifiNative.p2pFlush();
+ if (mWifiNative.p2pExtListen(true, 500, 500)) {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+ }
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ if (DBG) logd(getName() + " stop listen mode");
+ if (mWifiNative.p2pExtListen(false, 0, 0)) {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+ }
+ mWifiNative.p2pFlush();
+ break;
+ case WifiP2pManager.SET_CHANNEL:
+ Bundle p2pChannels = (Bundle) message.obj;
+ int lc = p2pChannels.getInt("lc", 0);
+ int oc = p2pChannels.getInt("oc", 0);
+ if (DBG) logd(getName() + " set listen and operating channel");
+ if (mWifiNative.p2pSetChannel(lc, oc)) {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+ }
+ break;
+ case SET_COUNTRY_CODE:
+ String countryCode = (String) message.obj;
+ countryCode = countryCode.toUpperCase(Locale.ROOT);
+ if (mLastSetCountryCode == null ||
+ countryCode.equals(mLastSetCountryCode) == false) {
+ if (mWifiNative.setCountryCode(countryCode)) {
+ mLastSetCountryCode = countryCode;
+ }
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ sendP2pDiscoveryChangedBroadcast(false);
+ sendP2pStateChangedBroadcast(false);
+ mNetworkInfo.setIsAvailable(false);
+
+ mLastSetCountryCode = null;
+ }
+ }
+
+ class InactiveState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSavedPeerConfig.invalidate();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiP2pManager.CONNECT:
+ if (DBG) logd(getName() + " sending connect");
+ WifiP2pConfig config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping connect requeset " + config);
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+ break;
+ }
+
+ mAutonomousGroup = false;
+ mWifiNative.p2pStopFind();
+ if (reinvokePersistentGroup(config)) {
+ transitionTo(mGroupNegotiationState);
+ } else {
+ transitionTo(mProvisionDiscoveryState);
+ }
+ mSavedPeerConfig = config;
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ // When discovery stops in inactive state, flush to clear
+ // state peer data
+ mWifiNative.p2pFlush();
+ mServiceDiscReqId = null;
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
+ config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping GO neg request " + config);
+ break;
+ }
+ mSavedPeerConfig = config;
+ mAutonomousGroup = false;
+ mJoinExistingGroup = false;
+ transitionTo(mUserAuthorizingNegotiationRequestState);
+ break;
+ case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
+ WifiP2pGroup group = (WifiP2pGroup) message.obj;
+ WifiP2pDevice owner = group.getOwner();
+
+ if (owner == null) {
+ loge("Ignored invitation from null owner");
+ break;
+ }
+
+ config = new WifiP2pConfig();
+ config.deviceAddress = group.getOwner().deviceAddress;
+
+ if (isConfigInvalid(config)) {
+ loge("Dropping invitation request " + config);
+ break;
+ }
+ mSavedPeerConfig = config;
+
+ //Check if we have the owner in peer list and use appropriate
+ //wps method. Default is to use PBC.
+ if ((owner = mPeers.get(owner.deviceAddress)) != null) {
+ if (owner.wpsPbcSupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+ } else if (owner.wpsKeypadSupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+ } else if (owner.wpsDisplaySupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+ }
+ }
+
+ mAutonomousGroup = false;
+ mJoinExistingGroup = true;
+ transitionTo(mUserAuthorizingInviteRequestState);
+ break;
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ //We let the supplicant handle the provision discovery response
+ //and wait instead for the GO_NEGOTIATION_REQUEST_EVENT.
+ //Handling provision discovery and issuing a p2p_connect before
+ //group negotiation comes through causes issues
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ mAutonomousGroup = true;
+ int netId = message.arg1;
+ boolean ret = false;
+ if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ // check if the go persistent group is present.
+ netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
+ if (netId != -1) {
+ ret = mWifiNative.p2pGroupAdd(netId);
+ } else {
+ ret = mWifiNative.p2pGroupAdd(true);
+ }
+ } else {
+ ret = mWifiNative.p2pGroupAdd(false);
+ }
+
+ if (ret) {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.ERROR);
+ // remain at this state.
+ }
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ if (DBG) logd(getName() + " group started");
+
+ // We hit this scenario when a persistent group is reinvoked
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ mAutonomousGroup = false;
+ deferMessage(message);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ loge("Unexpected group creation, remove " + mGroup);
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ }
+ break;
+ case WifiP2pManager.START_LISTEN:
+ if (DBG) logd(getName() + " start listen mode");
+ mWifiNative.p2pFlush();
+ if (mWifiNative.p2pExtListen(true, 500, 500)) {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+ }
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ if (DBG) logd(getName() + " stop listen mode");
+ if (mWifiNative.p2pExtListen(false, 0, 0)) {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+ }
+ mWifiNative.p2pFlush();
+ break;
+ case WifiP2pManager.SET_CHANNEL:
+ Bundle p2pChannels = (Bundle) message.obj;
+ int lc = p2pChannels.getInt("lc", 0);
+ int oc = p2pChannels.getInt("oc", 0);
+ if (DBG) logd(getName() + " set listen and operating channel");
+ if (mWifiNative.p2pSetChannel(lc, oc)) {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class GroupCreatingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
+ ++mGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case GROUP_CREATING_TIMED_OUT:
+ if (mGroupCreatingTimeoutIndex == message.arg1) {
+ if (DBG) logd("Group negotiation timed out");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ }
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ if (!mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) {
+ if (DBG) {
+ logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress +
+ "device " + device.deviceAddress);
+ }
+ // Do the regular device lost handling
+ ret = NOT_HANDLED;
+ break;
+ }
+ // Do nothing
+ if (DBG) logd("Add device to lost list " + device);
+ mPeersLostDuringConnection.updateSupplicantDetails(device);
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ /* Discovery will break negotiation */
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ //Do a supplicant p2p_cancel which only cancels an ongoing
+ //group negotiation. This will fail for a pending provision
+ //discovery or for a pending user action, but at the framework
+ //level, we always treat cancel as succeeded and enter
+ //an inactive state
+ mWifiNative.p2pCancelConnect();
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
+ break;
+ default:
+ ret = NOT_HANDLED;
+ }
+ return ret;
+ }
+ }
+
+ class UserAuthorizingNegotiationRequestState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case PEER_CONNECTION_USER_ACCEPT:
+ mWifiNative.p2pStopFind();
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ transitionTo(mGroupNegotiationState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected negotiation " + mSavedPeerConfig);
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+ class UserAuthorizingInviteRequestState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case PEER_CONNECTION_USER_ACCEPT:
+ mWifiNative.p2pStopFind();
+ if (!reinvokePersistentGroup(mSavedPeerConfig)) {
+ // Do negotiation when persistence fails
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ }
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ transitionTo(mGroupNegotiationState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+
+
+ class ProvisionDiscoveryState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ WifiP2pProvDiscEvent provDisc;
+ WifiP2pDevice device;
+ switch (message.what) {
+ case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ transitionTo(mGroupNegotiationState);
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ /* we already have the pin */
+ if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ mJoinExistingGroup = false;
+ transitionTo(mUserAuthorizingNegotiationRequestState);
+ }
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ mSavedPeerConfig.wps.pin = provDisc.pin;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ notifyInvitationSent(provDisc.pin, device.deviceAddress);
+ transitionTo(mGroupNegotiationState);
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+ loge("provision discovery failed");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class GroupNegotiationState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ // We ignore these right now, since we get a GROUP_STARTED notification
+ // afterwards
+ case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+ if (DBG) logd(getName() + " go success");
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ if (DBG) logd(getName() + " group started");
+
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ /*
+ * update cache information and set network id to mGroup.
+ */
+ updatePersistentNetworks(NO_RELOAD);
+ String devAddr = mGroup.getOwner().deviceAddress;
+ mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
+ mGroup.getNetworkName()));
+ }
+
+ if (mGroup.isGroupOwner()) {
+ /* Setting an idle time out on GO causes issues with certain scenarios
+ * on clients where it can be off-channel for longer and with the power
+ * save modes used.
+ *
+ * TODO: Verify multi-channel scenarios and supplicant behavior are
+ * better before adding a time out in future
+ */
+ //Set group idle timeout of 10 sec, to avoid GO beaconing incase of any
+ //failure during 4-way Handshake.
+ if (!mAutonomousGroup) {
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
+ }
+ startDhcpServer(mGroup.getInterface());
+ } else {
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
+ P2pStateMachine.this, mGroup.getInterface());
+ // TODO: We should use DHCP state machine PRE message like WifiStateMachine
+ mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
+ WifiP2pDevice groupOwner = mGroup.getOwner();
+ WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
+ if (peer != null) {
+ // update group owner details with peer details found at discovery
+ groupOwner.updateSupplicantDetails(peer);
+ mPeers.updateStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
+ sendPeersChangedBroadcast();
+ } else {
+ // A supplicant bug can lead to reporting an invalid
+ // group owner address (all zeroes) at times. Avoid a
+ // crash, but continue group creation since it is not
+ // essential.
+ logw("Unknown group owner " + groupOwner);
+ }
+ }
+ transitionTo(mGroupCreatedState);
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+ P2pStatus status = (P2pStatus) message.obj;
+ if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ break;
+ }
+ /* continue with group removal handling */
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ if (DBG) logd(getName() + " go failure");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ // A group formation failure is always followed by
+ // a group removed event. Flushing things at group formation
+ // failure causes supplicant issues. Ignore right now.
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ status = (P2pStatus) message.obj;
+ if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ break;
+ }
+ break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ status = (P2pStatus)message.obj;
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ // wait P2P_GROUP_STARTED_EVENT.
+ break;
+ }
+ loge("Invitation result " + status);
+ if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mSavedPeerConfig.netId;
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
+ }
+
+ // Reinvocation has failed, try group negotiation
+ mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ } else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
+
+ // Devices setting persistent_reconnect to 0 in wpa_supplicant
+ // always defer the invocation request and return
+ // "information is currently unable" error.
+ // So, try another way to connect for interoperability.
+ mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ } else if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ } else {
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class FrequencyConflictState extends State {
+ private AlertDialog mFrequencyConflictDialog;
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyFrequencyConflict();
+ }
+
+ private void notifyFrequencyConflict() {
+ logd("Notify frequency conflict");
+ Resources r = Resources.getSystem();
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
+ getDeviceName(mSavedPeerConfig.deviceAddress)))
+ .setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ sendMessage(DROP_WIFI_USER_ACCEPT);
+ }
+ })
+ .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ sendMessage(DROP_WIFI_USER_REJECT);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ sendMessage(DROP_WIFI_USER_REJECT);
+ }
+ })
+ .create();
+
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ mFrequencyConflictDialog = dialog;
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+ loge(getName() + "group sucess during freq conflict!");
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ loge(getName() + "group started after freq conflict, handle anyway");
+ deferMessage(message);
+ transitionTo(mGroupNegotiationState);
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ // Ignore failures since we retry again
+ break;
+ case DROP_WIFI_USER_REJECT:
+ // User rejected dropping wifi in favour of p2p
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ case DROP_WIFI_USER_ACCEPT:
+ // User accepted dropping wifi in favour of p2p
+ mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 1);
+ mTempoarilyDisconnectedWifi = true;
+ break;
+ case DISCONNECT_WIFI_RESPONSE:
+ // Got a response from wifistatemachine, retry p2p
+ if (DBG) logd(getName() + "Wifi disconnected, retry p2p");
+ transitionTo(mInactiveState);
+ sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ public void exit() {
+ if (mFrequencyConflictDialog != null) mFrequencyConflictDialog.dismiss();
+ }
+ }
+
+ class GroupCreatedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ // Once connected, peer config details are invalid
+ mSavedPeerConfig.invalidate();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+
+ updateThisDevice(WifiP2pDevice.CONNECTED);
+
+ //DHCP server has already been started if I am a group owner
+ if (mGroup.isGroupOwner()) {
+ setWifiP2pInfoOnGroupFormation(NetworkUtils.numericToInetAddress(SERVER_ADDRESS));
+ }
+
+ // In case of a negotiation group, connection changed is sent
+ // after a client joins. For autonomous, send now
+ if (mAutonomousGroup) {
+ sendP2pConnectionChangedBroadcast();
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.AP_STA_CONNECTED_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ String deviceAddress = device.deviceAddress;
+ // Clear timeout that was set when group was started.
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+ if (deviceAddress != null) {
+ if (mPeers.get(deviceAddress) != null) {
+ mGroup.addClient(mPeers.get(deviceAddress));
+ } else {
+ mGroup.addClient(deviceAddress);
+ }
+ mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
+ if (DBG) logd(getName() + " ap sta connected");
+ sendPeersChangedBroadcast();
+ } else {
+ loge("Connect on null device address, ignore");
+ }
+ sendP2pConnectionChangedBroadcast();
+ break;
+ case WifiMonitor.AP_STA_DISCONNECTED_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ deviceAddress = device.deviceAddress;
+ if (deviceAddress != null) {
+ mPeers.updateStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
+ if (mGroup.removeClient(deviceAddress)) {
+ if (DBG) logd("Removed client " + deviceAddress);
+ if (!mAutonomousGroup && mGroup.isClientListEmpty()) {
+ logd("Client list empty, remove non-persistent p2p group");
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ // We end up sending connection changed broadcast
+ // when this happens at exit()
+ } else {
+ // Notify when a client disconnects from group
+ sendP2pConnectionChangedBroadcast();
+ }
+ } else {
+ if (DBG) logd("Failed to remove client " + deviceAddress);
+ for (WifiP2pDevice c : mGroup.getClientList()) {
+ if (DBG) logd("client " + c.deviceAddress);
+ }
+ }
+ sendPeersChangedBroadcast();
+ if (DBG) logd(getName() + " ap sta disconnected");
+ } else {
+ loge("Disconnect on unknown device: " + device);
+ }
+ break;
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ DhcpResults dhcpResults = (DhcpResults) message.obj;
+ if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS &&
+ dhcpResults != null) {
+ if (DBG) logd("DhcpResults: " + dhcpResults);
+ setWifiP2pInfoOnGroupFormation(dhcpResults.serverAddress);
+ sendP2pConnectionChangedBroadcast();
+ //Turn on power save on client
+ mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
+ } else {
+ loge("DHCP failed");
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ }
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ if (DBG) logd(getName() + " remove group");
+ if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
+ transitionTo(mOngoingGroupRemovalState);
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+ } else {
+ handleGroupRemoved();
+ transitionTo(mInactiveState);
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ /* We do not listen to NETWORK_DISCONNECTION_EVENT for group removal
+ * handling since supplicant actually tries to reconnect after a temporary
+ * disconnect until group idle time out. Eventually, a group removal event
+ * will come when group has been removed.
+ *
+ * When there are connectivity issues during temporary disconnect, the application
+ * will also just remove the group.
+ *
+ * Treating network disconnection as group removal causes race conditions since
+ * supplicant would still maintain the group at that stage.
+ */
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ if (DBG) logd(getName() + " group removed");
+ handleGroupRemoved();
+ transitionTo(mInactiveState);
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ //Device loss for a connected device indicates it is not in discovery any more
+ if (mGroup.contains(device)) {
+ if (DBG) logd("Add device to lost list " + device);
+ mPeersLostDuringConnection.updateSupplicantDetails(device);
+ return HANDLED;
+ }
+ // Do the regular device lost handling
+ return NOT_HANDLED;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ sendMessage(WifiP2pManager.REMOVE_GROUP);
+ deferMessage(message);
+ break;
+ // This allows any client to join the GO during the
+ // WPS window
+ case WifiP2pManager.START_WPS:
+ WpsInfo wps = (WpsInfo) message.obj;
+ if (wps == null) {
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED);
+ break;
+ }
+ boolean ret = true;
+ if (wps.setup == WpsInfo.PBC) {
+ ret = mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+ } else {
+ if (wps.pin == null) {
+ String pin = mWifiNative.startWpsPinDisplay(mGroup.getInterface());
+ try {
+ Integer.parseInt(pin);
+ notifyInvitationSent(pin, "any");
+ } catch (NumberFormatException ignore) {
+ ret = false;
+ }
+ } else {
+ ret = mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+ wps.pin);
+ }
+ }
+ replyToMessage(message, ret ? WifiP2pManager.START_WPS_SUCCEEDED :
+ WifiP2pManager.START_WPS_FAILED);
+ break;
+ case WifiP2pManager.CONNECT:
+ WifiP2pConfig config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping connect requeset " + config);
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+ break;
+ }
+ logd("Inviting device : " + config.deviceAddress);
+ mSavedPeerConfig = config;
+ if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
+ mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ // TODO: figure out updating the status to declined when invitation is rejected
+ break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ P2pStatus status = (P2pStatus)message.obj;
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ break;
+ }
+ loge("Invitation result " + status);
+ if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mGroup.getNetworkId();
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ if (!removeClientFromList(netId,
+ mSavedPeerConfig.deviceAddress, false)) {
+ // not found the client on the list
+ loge("Already removed the client, ignore");
+ break;
+ }
+ // try invitation.
+ sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+ }
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
+ mSavedPeerConfig = new WifiP2pConfig();
+ mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
+ if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
+ mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+ } else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
+ mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+ mSavedPeerConfig.wps.pin = provDisc.pin;
+ } else {
+ mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+ }
+ transitionTo(mUserAuthorizingJoinState);
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ loge("Duplicate group creation event notice, ignore");
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ public void exit() {
+ updateThisDevice(WifiP2pDevice.AVAILABLE);
+ resetWifiP2pInfo();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+ sendP2pConnectionChangedBroadcast();
+ }
+ }
+
+ class UserAuthorizingJoinState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ //Ignore more client requests
+ break;
+ case PEER_CONNECTION_USER_ACCEPT:
+ //Stop discovery to avoid failure due to channel switch
+ mWifiNative.p2pStopFind();
+ if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+ mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+ } else {
+ mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+ mSavedPeerConfig.wps.pin);
+ }
+ transitionTo(mGroupCreatedState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected incoming request");
+ transitionTo(mGroupCreatedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+ class OngoingGroupRemovalState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ // Group removal ongoing. Multiple calls
+ // end up removing persisted network. Do nothing.
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+ break;
+ // Parent state will transition out of this state
+ // when removal is complete
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mWifiP2pInfo " + mWifiP2pInfo);
+ pw.println("mGroup " + mGroup);
+ pw.println("mSavedPeerConfig " + mSavedPeerConfig);
+ pw.println("mSavedP2pGroup " + mSavedP2pGroup);
+ pw.println();
+ }
+
+ private void sendP2pStateChangedBroadcast(boolean enabled) {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (enabled) {
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_ENABLED);
+ } else {
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_DISABLED);
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendP2pDiscoveryChangedBroadcast(boolean started) {
+ if (mDiscoveryStarted == started) return;
+ mDiscoveryStarted = started;
+
+ if (DBG) logd("discovery change broadcast " + started);
+
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ?
+ WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED :
+ WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendThisDeviceChangedBroadcast() {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendPeersChangedBroadcast() {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+ intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendP2pConnectionChangedBroadcast() {
+ if (DBG) logd("sending p2p connection changed broadcast");
+ Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
+ intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, new WifiP2pGroup(mGroup));
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED,
+ new NetworkInfo(mNetworkInfo));
+ }
+
+ private void sendP2pPersistentGroupsChangedBroadcast() {
+ if (DBG) logd("sending p2p persistent groups changed broadcast");
+ Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void startDhcpServer(String intf) {
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(intf);
+ ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
+ SERVER_ADDRESS), 24));
+ ifcg.setInterfaceUp();
+ mNwService.setInterfaceConfig(intf, ifcg);
+ /* This starts the dnsmasq server */
+ mNwService.startTethering(DHCP_RANGE);
+ } catch (Exception e) {
+ loge("Error configuring interface " + intf + ", :" + e);
+ return;
+ }
+
+ logd("Started Dhcp server on " + intf);
+ }
+
+ private void stopDhcpServer(String intf) {
+ try {
+ mNwService.stopTethering();
+ } catch (Exception e) {
+ loge("Error stopping Dhcp server" + e);
+ return;
+ }
+
+ logd("Stopped Dhcp server");
+ }
+
+ private void notifyP2pEnableFailure() {
+ Resources r = Resources.getSystem();
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_dialog_title))
+ .setMessage(r.getString(R.string.wifi_p2p_failed_message))
+ .setPositiveButton(r.getString(R.string.ok), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void addRowToDialog(ViewGroup group, int stringId, String value) {
+ Resources r = Resources.getSystem();
+ View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row,
+ group, false);
+ ((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId));
+ ((TextView) row.findViewById(R.id.value)).setText(value);
+ group.addView(row);
+ }
+
+ private void notifyInvitationSent(String pin, String peerAddress) {
+ Resources r = Resources.getSystem();
+
+ final View textEntryView = LayoutInflater.from(mContext)
+ .inflate(R.layout.wifi_p2p_dialog, null);
+
+ ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+ addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
+ addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
+ .setView(textEntryView)
+ .setPositiveButton(r.getString(R.string.ok), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void notifyInvitationReceived() {
+ Resources r = Resources.getSystem();
+ final WpsInfo wps = mSavedPeerConfig.wps;
+ final View textEntryView = LayoutInflater.from(mContext)
+ .inflate(R.layout.wifi_p2p_dialog, null);
+
+ ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+ addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName(
+ mSavedPeerConfig.deviceAddress));
+
+ final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
+ .setView(textEntryView)
+ .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (wps.setup == WpsInfo.KEYPAD) {
+ mSavedPeerConfig.wps.pin = pin.getText().toString();
+ }
+ if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig);
+ sendMessage(PEER_CONNECTION_USER_ACCEPT);
+ }
+ })
+ .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (DBG) logd(getName() + " ignore connect");
+ sendMessage(PEER_CONNECTION_USER_REJECT);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ if (DBG) logd(getName() + " ignore connect");
+ sendMessage(PEER_CONNECTION_USER_REJECT);
+ }
+ })
+ .create();
+
+ //make the enter pin area or the display pin area visible
+ switch (wps.setup) {
+ case WpsInfo.KEYPAD:
+ if (DBG) logd("Enter pin section visible");
+ textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
+ break;
+ case WpsInfo.DISPLAY:
+ if (DBG) logd("Shown pin section visible");
+ addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin);
+ break;
+ default:
+ break;
+ }
+
+ if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_APPLIANCE) ==
+ Configuration.UI_MODE_TYPE_APPLIANCE) {
+ // For appliance devices, add a key listener which accepts.
+ dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
+
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ // TODO: make the actual key come from a config value.
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ sendMessage(PEER_CONNECTION_USER_ACCEPT);
+ dialog.dismiss();
+ return true;
+ }
+ return false;
+ }
+ });
+ // TODO: add timeout for this dialog.
+ // TODO: update UI in appliance mode to tell user what to do.
+ }
+
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ /**
+ * Synchronize the persistent group list between
+ * wpa_supplicant and mGroups.
+ */
+ private void updatePersistentNetworks(boolean reload) {
+ String listStr = mWifiNative.listNetworks();
+ if (listStr == null) return;
+
+ boolean isSaveRequired = false;
+ String[] lines = listStr.split("\n");
+ if (lines == null) return;
+
+ if (reload) mGroups.clear();
+
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ if (result == null || result.length < 4) {
+ continue;
+ }
+ // network-id | ssid | bssid | flags
+ int netId = -1;
+ String ssid = result[1];
+ String bssid = result[2];
+ String flags = result[3];
+ try {
+ netId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (flags.indexOf("[CURRENT]") != -1) {
+ continue;
+ }
+ if (flags.indexOf("[P2P-PERSISTENT]") == -1) {
+ /*
+ * The unused profile is sometimes remained when the p2p group formation is failed.
+ * So, we clean up the p2p group here.
+ */
+ if (DBG) logd("clean up the unused persistent group. netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ isSaveRequired = true;
+ continue;
+ }
+
+ if (mGroups.contains(netId)) {
+ continue;
+ }
+
+ WifiP2pGroup group = new WifiP2pGroup();
+ group.setNetworkId(netId);
+ group.setNetworkName(ssid);
+ String mode = mWifiNative.getNetworkVariable(netId, "mode");
+ if (mode != null && mode.equals("3")) {
+ group.setIsGroupOwner(true);
+ }
+ if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) {
+ group.setOwner(mThisDevice);
+ } else {
+ WifiP2pDevice device = new WifiP2pDevice();
+ device.deviceAddress = bssid;
+ group.setOwner(device);
+ }
+ mGroups.add(group);
+ isSaveRequired = true;
+ }
+
+ if (reload || isSaveRequired) {
+ mWifiNative.saveConfig();
+ sendP2pPersistentGroupsChangedBroadcast();
+ }
+ }
+
+ /**
+ * A config is valid if it has a peer address that has already been
+ * discovered
+ * @return true if it is invalid, false otherwise
+ */
+ private boolean isConfigInvalid(WifiP2pConfig config) {
+ if (config == null) return true;
+ if (TextUtils.isEmpty(config.deviceAddress)) return true;
+ if (mPeers.get(config.deviceAddress) == null) return true;
+ return false;
+ }
+
+ /* TODO: The supplicant does not provide group capability changes as an event.
+ * Having it pushed as an event would avoid polling for this information right
+ * before a connection
+ */
+ private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
+ /* Fetch & update group capability from supplicant on the device */
+ int gc = mWifiNative.getGroupCapability(config.deviceAddress);
+ mPeers.updateGroupCapability(config.deviceAddress, gc);
+ return mPeers.get(config.deviceAddress);
+ }
+
+ /**
+ * Start a p2p group negotiation and display pin if necessary
+ * @param config for the peer
+ */
+ private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
+ WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+
+ String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
+ try {
+ Integer.parseInt(pin);
+ notifyInvitationSent(pin, config.deviceAddress);
+ } catch (NumberFormatException ignore) {
+ // do nothing if p2pConnect did not return a pin
+ }
+ }
+
+ /**
+ * Reinvoke a persistent group.
+ *
+ * @param config for the peer
+ * @return true on success, false on failure
+ */
+ private boolean reinvokePersistentGroup(WifiP2pConfig config) {
+ WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+
+ boolean join = dev.isGroupOwner();
+ String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
+ if (DBG) logd("target ssid is " + ssid + " join:" + join);
+
+ if (join && dev.isGroupLimit()) {
+ if (DBG) logd("target device reaches group limit.");
+
+ // if the target group has reached the limit,
+ // try group formation.
+ join = false;
+ } else if (join) {
+ int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
+ if (netId >= 0) {
+ // Skip WPS and start 4way handshake immediately.
+ if (!mWifiNative.p2pGroupAdd(netId)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ if (!join && dev.isDeviceLimit()) {
+ loge("target device reaches the device limit.");
+ return false;
+ }
+
+ if (!join && dev.isInvitationCapable()) {
+ int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ if (config.netId >= 0) {
+ if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
+ netId = config.netId;
+ }
+ } else {
+ netId = mGroups.getNetworkId(dev.deviceAddress);
+ }
+ if (netId < 0) {
+ netId = getNetworkIdFromClientList(dev.deviceAddress);
+ }
+ if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
+ if (netId >= 0) {
+ // Invoke the persistent group.
+ if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
+ // Save network id. It'll be used when an invitation result event is received.
+ config.netId = netId;
+ return true;
+ } else {
+ loge("p2pReinvoke() failed, update networks");
+ updatePersistentNetworks(RELOAD);
+ return false;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the network id of the group owner profile which has the p2p client with
+ * the specified device address in it's client list.
+ * If more than one persistent group of the same address is present in its client
+ * lists, return the first one.
+ *
+ * @param deviceAddress p2p device address.
+ * @return the network id. if not found, return -1.
+ */
+ private int getNetworkIdFromClientList(String deviceAddress) {
+ if (deviceAddress == null) return -1;
+
+ Collection<WifiP2pGroup> groups = mGroups.getGroupList();
+ for (WifiP2pGroup group : groups) {
+ int netId = group.getNetworkId();
+ String[] p2pClientList = getClientList(netId);
+ if (p2pClientList == null) continue;
+ for (String client : p2pClientList) {
+ if (deviceAddress.equalsIgnoreCase(client)) {
+ return netId;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return p2p client list associated with the specified network id.
+ * @param netId network id.
+ * @return p2p client list. if not found, return null.
+ */
+ private String[] getClientList(int netId) {
+ String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list");
+ if (p2pClients == null) {
+ return null;
+ }
+ return p2pClients.split(" ");
+ }
+
+ /**
+ * Remove the specified p2p client from the specified profile.
+ * @param netId network id of the profile.
+ * @param addr p2p client address to be removed.
+ * @param isRemovable if true, remove the specified profile if its client list becomes empty.
+ * @return whether removing the specified p2p client is successful or not.
+ */
+ private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
+ StringBuilder modifiedClientList = new StringBuilder();
+ String[] currentClientList = getClientList(netId);
+ boolean isClientRemoved = false;
+ if (currentClientList != null) {
+ for (String client : currentClientList) {
+ if (!client.equalsIgnoreCase(addr)) {
+ modifiedClientList.append(" ");
+ modifiedClientList.append(client);
+ } else {
+ isClientRemoved = true;
+ }
+ }
+ }
+ if (modifiedClientList.length() == 0 && isRemovable) {
+ // the client list is empty. so remove it.
+ if (DBG) logd("Remove unknown network");
+ mGroups.remove(netId);
+ return true;
+ }
+
+ if (!isClientRemoved) {
+ // specified p2p client is not found. already removed.
+ return false;
+ }
+
+ if (DBG) logd("Modified client list: " + modifiedClientList);
+ if (modifiedClientList.length() == 0) {
+ modifiedClientList.append("\"\"");
+ }
+ mWifiNative.setNetworkVariable(netId,
+ "p2p_client_list", modifiedClientList.toString());
+ mWifiNative.saveConfig();
+ return true;
+ }
+
+ private void setWifiP2pInfoOnGroupFormation(InetAddress serverInetAddress) {
+ mWifiP2pInfo.groupFormed = true;
+ mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
+ mWifiP2pInfo.groupOwnerAddress = serverInetAddress;
+ }
+
+ private void resetWifiP2pInfo() {
+ mWifiP2pInfo.groupFormed = false;
+ mWifiP2pInfo.isGroupOwner = false;
+ mWifiP2pInfo.groupOwnerAddress = null;
+ }
+
+ private String getDeviceName(String deviceAddress) {
+ WifiP2pDevice d = mPeers.get(deviceAddress);
+ if (d != null) {
+ return d.deviceName;
+ }
+ //Treat the address as name if there is no match
+ return deviceAddress;
+ }
+
+ private String getPersistedDeviceName() {
+ String deviceName = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_P2P_DEVICE_NAME);
+ if (deviceName == null) {
+ /* We use the 4 digits of the ANDROID_ID to have a friendly
+ * default that has low likelihood of collision with a peer */
+ String id = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ return "Android_" + id.substring(0,4);
+ }
+ return deviceName;
+ }
+
+ private boolean setAndPersistDeviceName(String devName) {
+ if (devName == null) return false;
+
+ if (!mWifiNative.setDeviceName(devName)) {
+ loge("Failed to set device name " + devName);
+ return false;
+ }
+
+ mThisDevice.deviceName = devName;
+ mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_P2P_DEVICE_NAME, devName);
+ sendThisDeviceChangedBroadcast();
+ return true;
+ }
+
+ private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
+ boolean success;
+
+ if (!wfdInfo.isWfdEnabled()) {
+ success = mWifiNative.setWfdEnable(false);
+ } else {
+ success =
+ mWifiNative.setWfdEnable(true)
+ && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
+ }
+
+ if (!success) {
+ loge("Failed to set wfd properties");
+ return false;
+ }
+
+ mThisDevice.wfdInfo = wfdInfo;
+ sendThisDeviceChangedBroadcast();
+ return true;
+ }
+
+ private void initializeP2pSettings() {
+ mWifiNative.setPersistentReconnect(true);
+ mThisDevice.deviceName = getPersistedDeviceName();
+ mWifiNative.setDeviceName(mThisDevice.deviceName);
+ // DIRECT-XY-DEVICENAME (XY is randomly generated)
+ mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+ mWifiNative.setDeviceType(mThisDevice.primaryDeviceType);
+ // Supplicant defaults to using virtual display with display
+ // which refers to a remote display. Use physical_display
+ mWifiNative.setConfigMethods("virtual_push_button physical_display keypad");
+ // STA has higher priority over P2P
+ mWifiNative.setConcurrencyPriority("sta");
+
+ mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
+ updateThisDevice(WifiP2pDevice.AVAILABLE);
+ if (DBG) logd("DeviceAddress: " + mThisDevice.deviceAddress);
+
+ mClientInfoList.clear();
+ mWifiNative.p2pFlush();
+ mWifiNative.p2pServiceFlush();
+ mServiceTransactionId = 0;
+ mServiceDiscReqId = null;
+
+ String countryCode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE);
+ if (countryCode != null && !countryCode.isEmpty()) {
+ mP2pStateMachine.sendMessage(SET_COUNTRY_CODE, countryCode);
+ }
+
+ updatePersistentNetworks(RELOAD);
+ }
+
+ private void updateThisDevice(int status) {
+ mThisDevice.status = status;
+ sendThisDeviceChangedBroadcast();
+ }
+
+ private void handleGroupCreationFailure() {
+ resetWifiP2pInfo();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
+ sendP2pConnectionChangedBroadcast();
+
+ // Remove only the peer we failed to connect to so that other devices discovered
+ // that have not timed out still remain in list for connection
+ boolean peersChanged = mPeers.remove(mPeersLostDuringConnection);
+ if (mPeers.remove(mSavedPeerConfig.deviceAddress) != null) {
+ peersChanged = true;
+ }
+ if (peersChanged) {
+ sendPeersChangedBroadcast();
+ }
+
+ mPeersLostDuringConnection.clear();
+ mServiceDiscReqId = null;
+ sendMessage(WifiP2pManager.DISCOVER_PEERS);
+ }
+
+ private void handleGroupRemoved() {
+ if (mGroup.isGroupOwner()) {
+ stopDhcpServer(mGroup.getInterface());
+ } else {
+ if (DBG) logd("stop DHCP client");
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
+ mDhcpStateMachine.doQuit();
+ mDhcpStateMachine = null;
+ }
+
+ try {
+ mNwService.clearInterfaceAddresses(mGroup.getInterface());
+ } catch (Exception e) {
+ loge("Failed to clear addresses " + e);
+ }
+ NetworkUtils.resetConnections(mGroup.getInterface(), NetworkUtils.RESET_ALL_ADDRESSES);
+
+ // Clear any timeout that was set. This is essential for devices
+ // that reuse the main p2p interface for a created group.
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+
+ boolean peersChanged = false;
+ // Remove only peers part of the group, so that other devices discovered
+ // that have not timed out still remain in list for connection
+ for (WifiP2pDevice d : mGroup.getClientList()) {
+ if (mPeers.remove(d)) peersChanged = true;
+ }
+ if (mPeers.remove(mGroup.getOwner())) peersChanged = true;
+ if (mPeers.remove(mPeersLostDuringConnection)) peersChanged = true;
+ if (peersChanged) {
+ sendPeersChangedBroadcast();
+ }
+
+ mGroup = null;
+ mPeersLostDuringConnection.clear();
+ mServiceDiscReqId = null;
+
+ if (mTempoarilyDisconnectedWifi) {
+ mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 0);
+ mTempoarilyDisconnectedWifi = false;
+ }
+ }
+
+ //State machine initiated requests can have replyTo set to null indicating
+ //there are no recipients, we ignore those reply actions
+ private void replyToMessage(Message msg, int what) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, int arg1) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ dstMsg.arg1 = arg1;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, Object obj) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ dstMsg.obj = obj;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ /* arg2 on the source message has a hash code that needs to be retained in replies
+ * see WifiP2pManager for details */
+ private Message obtainMessage(Message srcMsg) {
+ Message msg = Message.obtain();
+ msg.arg2 = srcMsg.arg2;
+ return msg;
+ }
+
+ @Override
+ protected void logd(String s) {
+ Slog.d(TAG, s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Slog.e(TAG, s);
+ }
+
+ /**
+ * Update service discovery request to wpa_supplicant.
+ */
+ private boolean updateSupplicantServiceRequest() {
+ clearSupplicantServiceRequest();
+
+ StringBuffer sb = new StringBuffer();
+ for (ClientInfo c: mClientInfoList.values()) {
+ int key;
+ WifiP2pServiceRequest req;
+ for (int i=0; i < c.mReqList.size(); i++) {
+ req = c.mReqList.valueAt(i);
+ if (req != null) {
+ sb.append(req.getSupplicantQuery());
+ }
+ }
+ }
+
+ if (sb.length() == 0) {
+ return false;
+ }
+
+ mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+ if (mServiceDiscReqId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clear service discovery request in wpa_supplicant
+ */
+ private void clearSupplicantServiceRequest() {
+ if (mServiceDiscReqId == null) return;
+
+ mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+ mServiceDiscReqId = null;
+ }
+
+ /* TODO: We could track individual service adds separately and avoid
+ * having to do update all service requests on every new request
+ */
+ private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ ++mServiceTransactionId;
+ //The Wi-Fi p2p spec says transaction id should be non-zero
+ if (mServiceTransactionId == 0) ++mServiceTransactionId;
+ req.setTransactionId(mServiceTransactionId);
+ clientInfo.mReqList.put(mServiceTransactionId, req);
+
+ if (mServiceDiscReqId == null) {
+ return true;
+ }
+
+ return updateSupplicantServiceRequest();
+ }
+
+ private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ //Application does not have transaction id information
+ //go through stored requests to remove
+ boolean removed = false;
+ for (int i=0; i<clientInfo.mReqList.size(); i++) {
+ if (req.equals(clientInfo.mReqList.valueAt(i))) {
+ removed = true;
+ clientInfo.mReqList.removeAt(i);
+ break;
+ }
+ }
+
+ if (!removed) return;
+
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private void clearServiceRequests(Messenger m) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ if (clientInfo.mReqList.size() == 0) {
+ return;
+ }
+
+ clientInfo.mReqList.clear();
+
+ if (clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove channel information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ if (!clientInfo.mServList.add(servInfo)) {
+ return false;
+ }
+
+ if (!mWifiNative.p2pServiceAdd(servInfo)) {
+ clientInfo.mServList.remove(servInfo);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ mWifiNative.p2pServiceDel(servInfo);
+
+ clientInfo.mServList.remove(servInfo);
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearLocalServices(Messenger m) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+ mWifiNative.p2pServiceDel(servInfo);
+ }
+
+ clientInfo.mServList.clear();
+ if (clientInfo.mReqList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearClientInfo(Messenger m) {
+ clearLocalServices(m);
+ clearServiceRequests(m);
+ }
+
+ /**
+ * Send the service response to the WifiP2pManager.Channel.
+ *
+ * @param resp
+ */
+ private void sendServiceResponse(WifiP2pServiceResponse resp) {
+ for (ClientInfo c : mClientInfoList.values()) {
+ WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
+ if (req != null) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.RESPONSE_SERVICE;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = resp;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * We dont get notifications of clients that have gone away.
+ * We detect this actively when services are added and throw
+ * them away.
+ *
+ * TODO: This can be done better with full async channels.
+ */
+ private void clearClientDeadChannels() {
+ ArrayList<Messenger> deadClients = new ArrayList<Messenger>();
+
+ for (ClientInfo c : mClientInfoList.values()) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.PING;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = null;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ deadClients.add(c.mMessenger);
+ }
+ }
+
+ for (Messenger m : deadClients) {
+ clearClientInfo(m);
+ }
+ }
+
+ /**
+ * Return the specified ClientInfo.
+ * @param m Messenger
+ * @param createIfNotExist if true and the specified channel info does not exist,
+ * create new client info.
+ * @return the specified ClientInfo.
+ */
+ private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+ ClientInfo clientInfo = mClientInfoList.get(m);
+
+ if (clientInfo == null && createIfNotExist) {
+ if (DBG) logd("add a new client");
+ clientInfo = new ClientInfo(m);
+ mClientInfoList.put(m, clientInfo);
+ }
+
+ return clientInfo;
+ }
+
+ }
+
+ /**
+ * Information about a particular client and we track the service discovery requests
+ * and the local services registered by the client.
+ */
+ private class ClientInfo {
+
+ /*
+ * A reference to WifiP2pManager.Channel handler.
+ * The response of this request is notified to WifiP2pManager.Channel handler
+ */
+ private Messenger mMessenger;
+
+ /*
+ * A service discovery request list.
+ */
+ private SparseArray<WifiP2pServiceRequest> mReqList;
+
+ /*
+ * A local service information list.
+ */
+ private List<WifiP2pServiceInfo> mServList;
+
+ private ClientInfo(Messenger m) {
+ mMessenger = m;
+ mReqList = new SparseArray();
+ mServList = new ArrayList<WifiP2pServiceInfo>();
+ }
+ }
+}
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
new file mode 100644
index 000000000..d7c6510cf
--- /dev/null
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2008, 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.
+ */
+
+#define LOG_TAG "wifi"
+
+#include "jni.h"
+#include <ScopedUtfChars.h>
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <utils/String16.h>
+
+#include "wifi.h"
+
+#define REPLY_BUF_SIZE 4096 // wpa_supplicant's maximum size.
+#define EVENT_BUF_SIZE 2048
+
+namespace android {
+
+static jint DBG = false;
+
+static bool doCommand(JNIEnv* env, jstring javaCommand,
+ char* reply, size_t reply_len) {
+ ScopedUtfChars command(env, javaCommand);
+ if (command.c_str() == NULL) {
+ return false; // ScopedUtfChars already threw on error.
+ }
+
+ if (DBG) {
+ ALOGD("doCommand: %s", command.c_str());
+ }
+
+ --reply_len; // Ensure we have room to add NUL termination.
+ if (::wifi_command(command.c_str(), reply, &reply_len) != 0) {
+ return false;
+ }
+
+ // Strip off trailing newline.
+ if (reply_len > 0 && reply[reply_len-1] == '\n') {
+ reply[reply_len-1] = '\0';
+ } else {
+ reply[reply_len] = '\0';
+ }
+ return true;
+}
+
+static jint doIntCommand(JNIEnv* env, jstring javaCommand) {
+ char reply[REPLY_BUF_SIZE];
+ if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
+ return -1;
+ }
+ return static_cast<jint>(atoi(reply));
+}
+
+static jboolean doBooleanCommand(JNIEnv* env, jstring javaCommand) {
+ char reply[REPLY_BUF_SIZE];
+ if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
+ return JNI_FALSE;
+ }
+ return (strcmp(reply, "OK") == 0);
+}
+
+// Send a command to the supplicant, and return the reply as a String.
+static jstring doStringCommand(JNIEnv* env, jstring javaCommand) {
+ char reply[REPLY_BUF_SIZE];
+ if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
+ return NULL;
+ }
+ return env->NewStringUTF(reply);
+}
+
+static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jobject)
+{
+ return (::is_wifi_driver_loaded() == 1);
+}
+
+static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject)
+{
+ return (::wifi_load_driver() == 0);
+}
+
+static jboolean android_net_wifi_unloadDriver(JNIEnv* env, jobject)
+{
+ return (::wifi_unload_driver() == 0);
+}
+
+static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject, jboolean p2pSupported)
+{
+ return (::wifi_start_supplicant(p2pSupported) == 0);
+}
+
+static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jobject, jboolean p2pSupported)
+{
+ return (::wifi_stop_supplicant(p2pSupported) == 0);
+}
+
+static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jobject)
+{
+ return (::wifi_connect_to_supplicant() == 0);
+}
+
+static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jobject)
+{
+ ::wifi_close_supplicant_connection();
+}
+
+static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject)
+{
+ char buf[EVENT_BUF_SIZE];
+ int nread = ::wifi_wait_for_event(buf, sizeof buf);
+ if (nread > 0) {
+ return env->NewStringUTF(buf);
+ } else {
+ return NULL;
+ }
+}
+
+static jboolean android_net_wifi_doBooleanCommand(JNIEnv* env, jobject, jstring javaCommand) {
+ return doBooleanCommand(env, javaCommand);
+}
+
+static jint android_net_wifi_doIntCommand(JNIEnv* env, jobject, jstring javaCommand) {
+ return doIntCommand(env, javaCommand);
+}
+
+static jstring android_net_wifi_doStringCommand(JNIEnv* env, jobject, jstring javaCommand) {
+ return doStringCommand(env,javaCommand);
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gWifiMethods[] = {
+ /* name, signature, funcPtr */
+
+ { "loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
+ { "isDriverLoaded", "()Z", (void *)android_net_wifi_isDriverLoaded },
+ { "unloadDriver", "()Z", (void *)android_net_wifi_unloadDriver },
+ { "startSupplicant", "(Z)Z", (void *)android_net_wifi_startSupplicant },
+ { "killSupplicant", "(Z)Z", (void *)android_net_wifi_killSupplicant },
+ { "connectToSupplicantNative", "()Z", (void *)android_net_wifi_connectToSupplicant },
+ { "closeSupplicantConnectionNative", "()V",
+ (void *)android_net_wifi_closeSupplicantConnection },
+ { "waitForEventNative", "()Ljava/lang/String;", (void*)android_net_wifi_waitForEvent },
+ { "doBooleanCommandNative", "(Ljava/lang/String;)Z", (void*)android_net_wifi_doBooleanCommand },
+ { "doIntCommandNative", "(Ljava/lang/String;)I", (void*)android_net_wifi_doIntCommand },
+ { "doStringCommandNative", "(Ljava/lang/String;)Ljava/lang/String;",
+ (void*) android_net_wifi_doStringCommand },
+};
+
+int register_android_net_wifi_WifiNative(JNIEnv* env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "com/android/server/wifi/WifiNative", gWifiMethods, NELEM(gWifiMethods));
+}
+
+
+/* User to register native functions */
+extern "C"
+jint Java_com_android_server_wifi_WifiNative_registerNatives(JNIEnv* env, jclass clazz) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "com/android/server/wifi/WifiNative", gWifiMethods, NELEM(gWifiMethods));
+}
+
+}; // namespace android
+