blob: 4b853987c94e2daecd7a37ecdc141bab62da96ec [file] [log] [blame]
/*
* 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 android.net;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A Utility class for handling NetworkRequests.
*
* Created by bearer-specific code to handle tracking requests, scores,
* network data and handle communicating with ConnectivityService. Two
* abstract methods: connect and disconnect are used to act on the
* underlying bearer code. Connect is called when we have a NetworkRequest
* and our score is better than the current handling network's score, while
* disconnect is used when ConnectivityService requests a disconnect.
*
* A bearer may have more than one NetworkAgent if it can simultaneously
* support separate networks (IMS / Internet / MMS Apns on cellular, or
* perhaps connections with different SSID or P2P for Wi-Fi). The bearer
* code should pass its NetworkAgents the NetworkRequests each NetworkAgent
* can handle, demultiplexing for different network types. The bearer code
* can also filter out requests it can never handle.
*
* Each NetworkAgent needs to be given a score and NetworkCapabilities for
* their potential network. While disconnected, the NetworkAgent will check
* each time its score changes or a NetworkRequest changes to see if
* the NetworkAgent can provide a higher scored network for a NetworkRequest
* that the NetworkAgent's NetworkCapabilties can satisfy. This condition will
* trigger a connect request via connect(). After connection, connection data
* should be given to the NetworkAgent by the bearer, including LinkProperties
* NetworkCapabilties and NetworkInfo. After that the NetworkAgent will register
* with ConnectivityService and forward the data on.
* @hide
*/
public abstract class NetworkAgent extends Handler {
private final SparseArray<NetworkRequestAndScore> mNetworkRequests = new SparseArray<>();
private boolean mConnectionRequested = false;
private AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
// TODO - this class shouldn't cache data or it runs the risk of getting out of sync
// Make the API require each of these when any is updated so we have the data we need,
// without caching.
private LinkProperties mLinkProperties;
private NetworkInfo mNetworkInfo;
private NetworkCapabilities mNetworkCapabilities;
private int mNetworkScore;
private boolean mRegistered = false;
private final Context mContext;
private AtomicBoolean mHasRequests = new AtomicBoolean(false);
// TODO - add a name member for logging purposes.
protected final Object mLockObj = new Object();
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
/**
* Sent by self to queue up a new/modified request.
* obj = NetworkRequestAndScore
*/
private static final int CMD_ADD_REQUEST = BASE + 1;
/**
* Sent by self to queue up the removal of a request.
* obj = NetworkRequest
*/
private static final int CMD_REMOVE_REQUEST = BASE + 2;
/**
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
*/
public static final int CMD_SUSPECT_BAD = BASE + 3;
/**
* Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
* ConnectivityService to pass the current NetworkInfo (connection state).
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
*/
public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 4;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
*/
public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 5;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
*/
public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 6;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
* arg1 = network score int
*/
public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 7;
public NetworkAgent(Looper looper, Context context, String logTag) {
super(looper);
LOG_TAG = logTag;
mContext = context;
}
/**
* When conditions are right, register with ConnectivityService.
* Connditions include having a well defined network and a request
* that justifies it. The NetworkAgent will remain registered until
* disconnected.
* TODO - this should have all data passed in rather than caching
*/
private void registerSelf() {
synchronized(mLockObj) {
if (!mRegistered && mConnectionRequested &&
mNetworkInfo != null && mNetworkInfo.isConnected() &&
mNetworkCapabilities != null &&
mLinkProperties != null &&
mNetworkScore != 0) {
if (DBG) log("Registering NetworkAgent");
mRegistered = true;
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(mNetworkInfo),
new LinkProperties(mLinkProperties),
new NetworkCapabilities(mNetworkCapabilities), mNetworkScore);
} else if (DBG && !mRegistered) {
String err = "Not registering due to ";
if (mConnectionRequested == false) err += "no Connect requested ";
if (mNetworkInfo == null) err += "null NetworkInfo ";
if (mNetworkInfo != null && mNetworkInfo.isConnected() == false) {
err += "NetworkInfo disconnected ";
}
if (mLinkProperties == null) err += "null LinkProperties ";
if (mNetworkCapabilities == null) err += "null NetworkCapabilities ";
if (mNetworkScore == 0) err += "null NetworkScore";
log(err);
}
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
synchronized (mLockObj) {
if (mAsyncChannel != null) {
log("Received new connection while already connected!");
} else {
if (DBG) log("NetworkAgent fully connected");
mAsyncChannel = new AsyncChannel();
mAsyncChannel.connected(null, this, msg.replyTo);
mAsyncChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_SUCCESSFUL);
}
}
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
if (DBG) log("CMD_CHANNEL_DISCONNECT");
if (mAsyncChannel != null) mAsyncChannel.disconnect();
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (DBG) log("NetworkAgent channel lost");
disconnect();
clear();
break;
}
case CMD_SUSPECT_BAD: {
log("Unhandled Message " + msg);
break;
}
case CMD_ADD_REQUEST: {
handleAddRequest(msg);
break;
}
case CMD_REMOVE_REQUEST: {
handleRemoveRequest(msg);
break;
}
}
}
private void clear() {
synchronized(mLockObj) {
mNetworkRequests.clear();
mHasRequests.set(false);
mConnectionRequested = false;
mAsyncChannel = null;
mRegistered = false;
}
}
private static class NetworkRequestAndScore {
NetworkRequest req;
int score;
NetworkRequestAndScore(NetworkRequest networkRequest, int score) {
req = networkRequest;
this.score = score;
}
}
private void handleAddRequest(Message msg) {
NetworkRequestAndScore n = (NetworkRequestAndScore)msg.obj;
// replaces old request, updating score
mNetworkRequests.put(n.req.requestId, n);
mHasRequests.set(true);
evalScores();
}
private void handleRemoveRequest(Message msg) {
NetworkRequest networkRequest = (NetworkRequest)msg.obj;
if (mNetworkRequests.get(networkRequest.requestId) != null) {
mNetworkRequests.remove(networkRequest.requestId);
if (mNetworkRequests.size() == 0) mHasRequests.set(false);
evalScores();
}
}
/**
* called to go through our list of requests and see if we're
* good enough to try connecting.
*
* Only does connects - we disconnect when requested via
* CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection
* between modules (bearer or ConnectivityService dies) or more commonly
* when the NetworkInfo reports to ConnectivityService it is disconnected.
*/
private void evalScores() {
if (mConnectionRequested) {
// already trying
return;
}
for (int i=0; i < mNetworkRequests.size(); i++) {
int score = mNetworkRequests.valueAt(i).score;
if (score < mNetworkScore) {
// have a request that has a lower scored network servicing it
// (or no network) than we could provide, so lets connect!
mConnectionRequested = true;
connect();
return;
}
}
}
public void addNetworkRequest(NetworkRequest networkRequest, int score) {
if (DBG) log("adding NetworkRequest " + networkRequest + " with score " + score);
sendMessage(obtainMessage(CMD_ADD_REQUEST,
new NetworkRequestAndScore(networkRequest, score)));
}
public void removeNetworkRequest(NetworkRequest networkRequest) {
if (DBG) log("removing NetworkRequest " + networkRequest);
sendMessage(obtainMessage(CMD_REMOVE_REQUEST, networkRequest));
}
/**
* Called by the bearer code when it has new LinkProperties data.
* If we're a registered NetworkAgent, this new data will get forwarded on,
* otherwise we store a copy in anticipation of registering. This call
* may also prompt registration if it causes the NetworkAgent to meet
* the conditions (fully configured, connected, satisfys a request and
* has sufficient score).
*/
public void sendLinkProperties(LinkProperties linkProperties) {
linkProperties = new LinkProperties(linkProperties);
synchronized(mLockObj) {
mLinkProperties = linkProperties;
if (mAsyncChannel != null) {
mAsyncChannel.sendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, linkProperties);
} else {
registerSelf();
}
}
}
/**
* Called by the bearer code when it has new NetworkInfo data.
* If we're a registered NetworkAgent, this new data will get forwarded on,
* otherwise we store a copy in anticipation of registering. This call
* may also prompt registration if it causes the NetworkAgent to meet
* the conditions (fully configured, connected, satisfys a request and
* has sufficient score).
*/
public void sendNetworkInfo(NetworkInfo networkInfo) {
networkInfo = new NetworkInfo(networkInfo);
synchronized(mLockObj) {
mNetworkInfo = networkInfo;
if (mAsyncChannel != null) {
mAsyncChannel.sendMessage(EVENT_NETWORK_INFO_CHANGED, networkInfo);
} else {
registerSelf();
}
}
}
/**
* Called by the bearer code when it has new NetworkCapabilities data.
* If we're a registered NetworkAgent, this new data will get forwarded on,
* otherwise we store a copy in anticipation of registering. This call
* may also prompt registration if it causes the NetworkAgent to meet
* the conditions (fully configured, connected, satisfys a request and
* has sufficient score).
* Note that if these capabilities make the network non-useful,
* ConnectivityServce will tear this network down.
*/
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
networkCapabilities = new NetworkCapabilities(networkCapabilities);
synchronized(mLockObj) {
mNetworkCapabilities = networkCapabilities;
if (mAsyncChannel != null) {
mAsyncChannel.sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, networkCapabilities);
} else {
registerSelf();
}
}
}
public NetworkCapabilities getNetworkCapabilities() {
synchronized(mLockObj) {
return new NetworkCapabilities(mNetworkCapabilities);
}
}
/**
* Called by the bearer code when it has a new score for this network.
* If we're a registered NetworkAgent, this new data will get forwarded on,
* otherwise we store a copy.
*/
public synchronized void sendNetworkScore(int score) {
synchronized(mLockObj) {
mNetworkScore = score;
evalScores();
if (mAsyncChannel != null) {
mAsyncChannel.sendMessage(EVENT_NETWORK_SCORE_CHANGED, mNetworkScore);
} else {
registerSelf();
}
}
}
public boolean hasRequests() {
return mHasRequests.get();
}
public boolean isConnectionRequested() {
synchronized(mLockObj) {
return mConnectionRequested;
}
}
abstract protected void connect();
abstract protected void disconnect();
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
}