diff options
author | Paul Jensen <pauljensen@google.com> | 2014-05-09 12:47:55 -0400 |
---|---|---|
committer | Lorenzo Colitti <lorenzo@google.com> | 2014-05-14 03:56:34 -0700 |
commit | ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7 (patch) | |
tree | 1e20685b6f4d6bb1581f89d10288504c4698cc6e /services/core/java/com/android/server/connectivity/NetworkMonitor.java | |
parent | 992f25257938ecc0378514f21c6e6e6375272976 (diff) | |
download | frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.zip frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.tar.gz frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.tar.bz2 |
Add NetworkMonitor.
At present the network evaluation / captive portal detection
is disabled pending addition of API to bind socket to network.
Change-Id: I5d1f5dc86d4dd9481d52dd45d6da0732054c8315
Diffstat (limited to 'services/core/java/com/android/server/connectivity/NetworkMonitor.java')
-rw-r--r-- | services/core/java/com/android/server/connectivity/NetworkMonitor.java | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java new file mode 100644 index 0000000..47789b1 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2014 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.connectivity; + +import android.content.Context; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.Message; +import android.os.SystemProperties; +import android.provider.Settings; + +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.connectivity.NetworkAgentInfo; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URL; + +/** + * {@hide} + */ +public class NetworkMonitor extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "NetworkMonitor"; + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final int SOCKET_TIMEOUT_MS = 10000; + + private static final int BASE = Protocol.BASE_NETWORK_MONITOR; + + /** + * Inform NetworkMonitor that their network is connected. + * Initiates Network Validation. + */ + public static final int CMD_NETWORK_CONNECTED = BASE + 1; + + /** + * Inform ConnectivityService that the network is validated. + * obj = NetworkAgentInfo + */ + public static final int EVENT_NETWORK_VALIDATED = BASE + 2; + + /** + * Inform NetworkMonitor to linger a network. The Monitor should + * start a timer and/or start watching for zero live connections while + * moving towards LINGER_COMPLETE. After the Linger period expires + * (or other events mark the end of the linger state) the LINGER_COMPLETE + * event should be sent and the network will be shut down. If a + * CMD_NETWORK_CONNECTED happens before the LINGER completes + * it indicates further desire to keep the network alive and so + * the LINGER is aborted. + */ + public static final int CMD_NETWORK_LINGER = BASE + 3; + + /** + * Message to self indicating linger delay has expired. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_LINGER_EXPIRED = BASE + 4; + + /** + * Inform ConnectivityService that the network LINGER period has + * expired. + * obj = NetworkAgentInfo + */ + public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; + + /** + * Message to self indicating it's time to check for a captive portal again. + * TODO - Remove this once broadcast intents are used to communicate with + * apps to log into captive portals. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6; + + /** + * Message to self indicating it's time to evaluate a network's connectivity. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_REEVALUATE = BASE + 7; + + /** + * Message to self indicating network evaluation is complete. + * arg1 = Token to ignore old messages. + * arg2 = HTTP response code of network evaluation. + */ + private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8; + + /** + * Inform NetworkMonitor that the network has disconnected. + */ + public static final int CMD_NETWORK_DISCONNECTED = BASE + 9; + + /** + * Force evaluation even if it has succeeded in the past. + */ + public static final int CMD_FORCE_REEVALUATION = BASE + 10; + + private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; + // Default to 30s linger time-out. + private static final int DEFAULT_LINGER_DELAY_MS = 30000; + private final int mLingerDelayMs; + private int mLingerToken = 0; + + private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000; + private int mCaptivePortalReevaluateToken = 0; + + // Negative values disable reevaluation. + private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay"; + // Default to 5s reevaluation delay. + private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000; + private final int mReevaluateDelayMs; + private int mReevaluateToken = 0; + + private final Context mContext; + private final Handler mConnectivityServiceHandler; + private final NetworkAgentInfo mNetworkAgentInfo; + + private String mServer; + private boolean mIsCaptivePortalCheckEnabled = false; + + private State mDefaultState = new DefaultState(); + private State mOfflineState = new OfflineState(); + private State mValidatedState = new ValidatedState(); + private State mEvaluatingState = new EvaluatingState(); + private State mCaptivePortalState = new CaptivePortalState(); + private State mLingeringState = new LingeringState(); + + public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) { + // Add suffix indicating which NetworkMonitor we're talking about. + super(TAG + networkAgentInfo.name()); + + mContext = context; + mConnectivityServiceHandler = handler; + mNetworkAgentInfo = networkAgentInfo; + + addState(mDefaultState); + addState(mOfflineState, mDefaultState); + addState(mValidatedState, mDefaultState); + addState(mEvaluatingState, mDefaultState); + addState(mCaptivePortalState, mDefaultState); + addState(mLingeringState, mDefaultState); + setInitialState(mOfflineState); + + mServer = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_SERVER); + if (mServer == null) mServer = DEFAULT_SERVER; + + mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); + mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, + DEFAULT_REEVALUATE_DELAY_MS); + + // TODO: Enable this when we're ready. + // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), + // Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + + start(); + } + + private class DefaultState extends State { + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_LINGER: + if (DBG) log("Lingering"); + transitionTo(mLingeringState); + break; + case CMD_NETWORK_CONNECTED: + if (DBG) log("Connected"); + transitionTo(mEvaluatingState); + break; + case CMD_NETWORK_DISCONNECTED: + if (DBG) log("Disconnected"); + transitionTo(mOfflineState); + break; + case CMD_FORCE_REEVALUATION: + if (DBG) log("Forcing reevaluation"); + transitionTo(mEvaluatingState); + break; + default: + break; + } + return HANDLED; + } + } + + private class OfflineState extends State { + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + return NOT_HANDLED; + } + } + + private class ValidatedState extends State { + @Override + public void enter() { + if (DBG) log("Validated"); + mConnectivityServiceHandler.sendMessage( + obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo)); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_CONNECTED: + transitionTo(mValidatedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class EvaluatingState extends State { + private class EvaluateInternetConnectivity extends Thread { + private int mToken; + EvaluateInternetConnectivity(int token) { + mToken = token; + } + public void run() { + sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal()); + } + } + + @Override + public void enter() { + sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_REEVALUATE: + if (message.arg1 != mReevaluateToken) + break; + // If network provides no internet connectivity adjust evaluation. + if (mNetworkAgentInfo.networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + // TODO: Try to verify something works. Do all gateways respond to pings? + transitionTo(mValidatedState); + } + // Kick off a thread to perform internet connectivity evaluation. + Thread thread = new EvaluateInternetConnectivity(mReevaluateToken); + thread.run(); + break; + case EVENT_REEVALUATION_COMPLETE: + if (message.arg1 != mReevaluateToken) + break; + int httpResponseCode = message.arg2; + if (httpResponseCode == 204) { + transitionTo(mValidatedState); + } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { + transitionTo(mCaptivePortalState); + } else { + if (mReevaluateDelayMs >= 0) { + Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); + } + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + // TODO: Until we add an intent from the app handling captive portal + // login we'll just re-evaluate after a delay. + private class CaptivePortalState extends State { + @Override + public void enter() { + Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE, + ++mCaptivePortalReevaluateToken, 0); + sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_CAPTIVE_PORTAL_REEVALUATE: + if (message.arg1 != mCaptivePortalReevaluateToken) + break; + transitionTo(mEvaluatingState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class LingeringState extends State { + @Override + public void enter() { + Message message = obtainMessage(CMD_LINGER_EXPIRED, ++mLingerToken, 0); + sendMessageDelayed(message, mLingerDelayMs); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_CONNECTED: + // Go straight to active as we've already evaluated. + transitionTo(mValidatedState); + break; + case CMD_LINGER_EXPIRED: + if (message.arg1 != mLingerToken) + break; + mConnectivityServiceHandler.sendMessage( + obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo)); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + /** + * Do a URL fetch on a known server to see if we get the data we expect. + * Returns HTTP response code. + */ + private int isCaptivePortal() { + if (!mIsCaptivePortalCheckEnabled) return 204; + + String urlString = "http://" + mServer + "/generate_204"; + if (DBG) log("Checking " + urlString); + HttpURLConnection urlConnection = null; + Socket socket = null; + int httpResponseCode = 500; + try { + URL url = new URL(urlString); + if (false) { + // TODO: Need to add URLConnection.setNetwork() before we can enable. + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } else { + socket = new Socket(); + // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId); + InetSocketAddress address = new InetSocketAddress(url.getHost(), 80); + // TODO: address = new InetSocketAddress( + // getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80); + socket.connect(address); + BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream())); + OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream()); + writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n"); + writer.flush(); + String response = reader.readLine(); + if (response.startsWith("HTTP/1.1 ")) { + httpResponseCode = Integer.parseInt(response.substring(9, 12)); + } + } + if (DBG) log("isCaptivePortal: ret=" + httpResponseCode); + } catch (IOException e) { + if (DBG) log("Probably not a portal: exception " + e); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // Ignore + } + } + } + return httpResponseCode; + } +} |