/*
|
* 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.annotation.UnsupportedAppUsage;
|
import android.content.Context;
|
import android.os.Build;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.Messenger;
|
import android.util.Log;
|
import android.util.SparseArray;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.AsyncChannel;
|
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.internal.util.Protocol;
|
|
import java.io.FileDescriptor;
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
/**
|
* A NetworkFactory is an entity that creates NetworkAgent objects.
|
* The bearers register with ConnectivityService using {@link #register} and
|
* their factory will start receiving scored NetworkRequests. NetworkRequests
|
* can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
|
* overridden function. All of these can be dynamic - changing NetworkCapabilities
|
* or score forces re-evaluation of all current requests.
|
*
|
* If any requests pass the filter some overrideable functions will be called.
|
* If the bearer only cares about very simple start/stopNetwork callbacks, those
|
* functions can be overridden. If the bearer needs more interaction, it can
|
* override addNetworkRequest and removeNetworkRequest which will give it each
|
* request that passes their current filters.
|
* @hide
|
**/
|
public class NetworkFactory extends Handler {
|
/** @hide */
|
public static class SerialNumber {
|
// Guard used by no network factory.
|
public static final int NONE = -1;
|
// A hardcoded serial number for NetworkAgents representing VPNs. These agents are
|
// not created by any factory, so they use this constant for clarity instead of NONE.
|
public static final int VPN = -2;
|
private static final AtomicInteger sNetworkFactorySerialNumber = new AtomicInteger(1);
|
/** Returns a unique serial number for a factory. */
|
public static final int nextSerialNumber() {
|
return sNetworkFactorySerialNumber.getAndIncrement();
|
}
|
}
|
|
private static final boolean DBG = true;
|
private static final boolean VDBG = false;
|
|
private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
|
/**
|
* Pass a network request to the bearer. If the bearer believes it can
|
* satisfy the request it should connect to the network and create a
|
* NetworkAgent. Once the NetworkAgent is fully functional it will
|
* register itself with ConnectivityService using registerNetworkAgent.
|
* If the bearer cannot immediately satisfy the request (no network,
|
* user disabled the radio, lower-scored network) it should remember
|
* any NetworkRequests it may be able to satisfy in the future. It may
|
* disregard any that it will never be able to service, for example
|
* those requiring a different bearer.
|
* msg.obj = NetworkRequest
|
* msg.arg1 = score - the score of the network currently satisfying this
|
* request. If this bearer knows in advance it cannot
|
* exceed this score it should not try to connect, holding the request
|
* for the future.
|
* Note that subsequent events may give a different (lower
|
* or higher) score for this request, transmitted to each
|
* NetworkFactory through additional CMD_REQUEST_NETWORK msgs
|
* with the same NetworkRequest but an updated score.
|
* Also, network conditions may change for this bearer
|
* allowing for a better score in the future.
|
* msg.arg2 = the serial number of the factory currently responsible for the
|
* NetworkAgent handling this request, or SerialNumber.NONE if none.
|
*/
|
public static final int CMD_REQUEST_NETWORK = BASE;
|
|
/**
|
* Cancel a network request
|
* msg.obj = NetworkRequest
|
*/
|
public static final int CMD_CANCEL_REQUEST = BASE + 1;
|
|
/**
|
* Internally used to set our best-guess score.
|
* msg.arg1 = new score
|
*/
|
private static final int CMD_SET_SCORE = BASE + 2;
|
|
/**
|
* Internally used to set our current filter for coarse bandwidth changes with
|
* technology changes.
|
* msg.obj = new filter
|
*/
|
private static final int CMD_SET_FILTER = BASE + 3;
|
|
/**
|
* Sent by NetworkFactory to ConnectivityService to indicate that a request is
|
* unfulfillable.
|
* @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
|
*/
|
public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
|
|
private final Context mContext;
|
private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
|
private AsyncChannel mAsyncChannel;
|
private final String LOG_TAG;
|
|
private final SparseArray<NetworkRequestInfo> mNetworkRequests =
|
new SparseArray<NetworkRequestInfo>();
|
|
private int mScore;
|
private NetworkCapabilities mCapabilityFilter;
|
|
private int mRefCount = 0;
|
private Messenger mMessenger = null;
|
private int mSerialNumber;
|
|
@UnsupportedAppUsage
|
public NetworkFactory(Looper looper, Context context, String logTag,
|
NetworkCapabilities filter) {
|
super(looper);
|
LOG_TAG = logTag;
|
mContext = context;
|
mCapabilityFilter = filter;
|
}
|
|
public void register() {
|
if (DBG) log("Registering NetworkFactory");
|
if (mMessenger == null) {
|
mMessenger = new Messenger(this);
|
mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
|
LOG_TAG);
|
}
|
}
|
|
public void unregister() {
|
if (DBG) log("Unregistering NetworkFactory");
|
if (mMessenger != null) {
|
ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
|
mMessenger = null;
|
}
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
switch (msg.what) {
|
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
|
if (mAsyncChannel != null) {
|
log("Received new connection while already connected!");
|
break;
|
}
|
if (VDBG) log("NetworkFactory fully connected");
|
AsyncChannel ac = new AsyncChannel();
|
ac.connected(null, this, msg.replyTo);
|
ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
|
AsyncChannel.STATUS_SUCCESSFUL);
|
mAsyncChannel = ac;
|
for (Message m : mPreConnectedQueue) {
|
ac.sendMessage(m);
|
}
|
mPreConnectedQueue.clear();
|
break;
|
}
|
case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
|
if (VDBG) log("CMD_CHANNEL_DISCONNECT");
|
if (mAsyncChannel != null) {
|
mAsyncChannel.disconnect();
|
mAsyncChannel = null;
|
}
|
break;
|
}
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
|
if (DBG) log("NetworkFactory channel lost");
|
mAsyncChannel = null;
|
break;
|
}
|
case CMD_REQUEST_NETWORK: {
|
handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
|
break;
|
}
|
case CMD_CANCEL_REQUEST: {
|
handleRemoveRequest((NetworkRequest) msg.obj);
|
break;
|
}
|
case CMD_SET_SCORE: {
|
handleSetScore(msg.arg1);
|
break;
|
}
|
case CMD_SET_FILTER: {
|
handleSetFilter((NetworkCapabilities) msg.obj);
|
break;
|
}
|
}
|
}
|
|
private class NetworkRequestInfo {
|
public final NetworkRequest request;
|
public int score;
|
public boolean requested; // do we have a request outstanding, limited by score
|
public int factorySerialNumber;
|
|
NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
|
this.request = request;
|
this.score = score;
|
this.requested = false;
|
this.factorySerialNumber = factorySerialNumber;
|
}
|
|
@Override
|
public String toString() {
|
return "{" + request + ", score=" + score + ", requested=" + requested + "}";
|
}
|
}
|
|
/**
|
* Add a NetworkRequest that the bearer may want to attempt to satisfy.
|
* @see #CMD_REQUEST_NETWORK
|
*
|
* @param request the request to handle.
|
* @param score the score of the NetworkAgent currently satisfying this request.
|
* @param servingFactorySerialNumber the serial number of the NetworkFactory that
|
* created the NetworkAgent currently satisfying this request.
|
*/
|
// TODO : remove this method. It is a stopgap measure to help sheperding a number
|
// of dependent changes that would conflict throughout the automerger graph. Having this
|
// temporarily helps with the process of going through with all these dependent changes across
|
// the entire tree.
|
@VisibleForTesting
|
protected void handleAddRequest(NetworkRequest request, int score) {
|
handleAddRequest(request, score, SerialNumber.NONE);
|
}
|
|
/**
|
* Add a NetworkRequest that the bearer may want to attempt to satisfy.
|
* @see #CMD_REQUEST_NETWORK
|
*
|
* @param request the request to handle.
|
* @param score the score of the NetworkAgent currently satisfying this request.
|
* @param servingFactorySerialNumber the serial number of the NetworkFactory that
|
* created the NetworkAgent currently satisfying this request.
|
*/
|
@VisibleForTesting
|
protected void handleAddRequest(NetworkRequest request, int score,
|
int servingFactorySerialNumber) {
|
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
|
if (n == null) {
|
if (DBG) {
|
log("got request " + request + " with score " + score
|
+ " and serial " + servingFactorySerialNumber);
|
}
|
n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
|
mNetworkRequests.put(n.request.requestId, n);
|
} else {
|
if (VDBG) {
|
log("new score " + score + " for exisiting request " + request
|
+ " with serial " + servingFactorySerialNumber);
|
}
|
n.score = score;
|
n.factorySerialNumber = servingFactorySerialNumber;
|
}
|
if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
|
|
evalRequest(n);
|
}
|
|
@VisibleForTesting
|
protected void handleRemoveRequest(NetworkRequest request) {
|
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
|
if (n != null) {
|
mNetworkRequests.remove(request.requestId);
|
if (n.requested) releaseNetworkFor(n.request);
|
}
|
}
|
|
private void handleSetScore(int score) {
|
mScore = score;
|
evalRequests();
|
}
|
|
private void handleSetFilter(NetworkCapabilities netCap) {
|
mCapabilityFilter = netCap;
|
evalRequests();
|
}
|
|
/**
|
* Overridable function to provide complex filtering.
|
* Called for every request every time a new NetworkRequest is seen
|
* and whenever the filterScore or filterNetworkCapabilities change.
|
*
|
* acceptRequest can be overridden to provide complex filter behavior
|
* for the incoming requests
|
*
|
* For output, this class will call {@link #needNetworkFor} and
|
* {@link #releaseNetworkFor} for every request that passes the filters.
|
* If you don't need to see every request, you can leave the base
|
* implementations of those two functions and instead override
|
* {@link #startNetwork} and {@link #stopNetwork}.
|
*
|
* If you want to see every score fluctuation on every request, set
|
* your score filter to a very high number and watch {@link #needNetworkFor}.
|
*
|
* @return {@code true} to accept the request.
|
*/
|
public boolean acceptRequest(NetworkRequest request, int score) {
|
return true;
|
}
|
|
private void evalRequest(NetworkRequestInfo n) {
|
if (VDBG) {
|
log("evalRequest");
|
log(" n.requests = " + n.requested);
|
log(" n.score = " + n.score);
|
log(" mScore = " + mScore);
|
log(" n.factorySerialNumber = " + n.factorySerialNumber);
|
log(" mSerialNumber = " + mSerialNumber);
|
}
|
if (shouldNeedNetworkFor(n)) {
|
if (VDBG) log(" needNetworkFor");
|
needNetworkFor(n.request, n.score);
|
n.requested = true;
|
} else if (shouldReleaseNetworkFor(n)) {
|
if (VDBG) log(" releaseNetworkFor");
|
releaseNetworkFor(n.request);
|
n.requested = false;
|
} else {
|
if (VDBG) log(" done");
|
}
|
}
|
|
private boolean shouldNeedNetworkFor(NetworkRequestInfo n) {
|
// If this request is already tracked, it doesn't qualify for need
|
return !n.requested
|
// If the score of this request is higher or equal to that of this factory and some
|
// other factory is responsible for it, then this factory should not track the request
|
// because it has no hope of satisfying it.
|
&& (n.score < mScore || n.factorySerialNumber == mSerialNumber)
|
// If this factory can't satisfy the capability needs of this request, then it
|
// should not be tracked.
|
&& n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
|
// Finally if the concrete implementation of the factory rejects the request, then
|
// don't track it.
|
&& acceptRequest(n.request, n.score);
|
}
|
|
private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) {
|
// Don't release a request that's not tracked.
|
return n.requested
|
// The request should be released if it can't be satisfied by this factory. That
|
// means either of the following conditions are met :
|
// - Its score is too high to be satisfied by this factory and it's not already
|
// assigned to the factory
|
// - This factory can't satisfy the capability needs of the request
|
// - The concrete implementation of the factory rejects the request
|
&& ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
|
|| !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
|
mCapabilityFilter)
|
|| !acceptRequest(n.request, n.score));
|
}
|
|
private void evalRequests() {
|
for (int i = 0; i < mNetworkRequests.size(); i++) {
|
NetworkRequestInfo n = mNetworkRequests.valueAt(i);
|
evalRequest(n);
|
}
|
}
|
|
/**
|
* Post a command, on this NetworkFactory Handler, to re-evaluate all
|
* oustanding requests. Can be called from a factory implementation.
|
*/
|
protected void reevaluateAllRequests() {
|
post(() -> {
|
evalRequests();
|
});
|
}
|
|
/**
|
* Can be called by a factory to release a request as unfulfillable: the request will be
|
* removed, and the caller will get a
|
* {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
|
* returns.
|
*
|
* Note: this should only be called by factory which KNOWS that it is the ONLY factory which
|
* is able to fulfill this request!
|
*/
|
protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
|
post(() -> {
|
if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
|
Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
|
if (mAsyncChannel != null) {
|
mAsyncChannel.sendMessage(msg);
|
} else {
|
mPreConnectedQueue.add(msg);
|
}
|
});
|
}
|
|
// override to do simple mode (request independent)
|
protected void startNetwork() { }
|
protected void stopNetwork() { }
|
|
// override to do fancier stuff
|
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
|
if (++mRefCount == 1) startNetwork();
|
}
|
|
protected void releaseNetworkFor(NetworkRequest networkRequest) {
|
if (--mRefCount == 0) stopNetwork();
|
}
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
public void setScoreFilter(int score) {
|
sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
|
}
|
|
public void setCapabilityFilter(NetworkCapabilities netCap) {
|
sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
|
}
|
|
@VisibleForTesting
|
protected int getRequestCount() {
|
return mNetworkRequests.size();
|
}
|
|
public int getSerialNumber() {
|
return mSerialNumber;
|
}
|
|
protected void log(String s) {
|
Log.d(LOG_TAG, s);
|
}
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
pw.println(toString());
|
pw.increaseIndent();
|
for (int i = 0; i < mNetworkRequests.size(); i++) {
|
pw.println(mNetworkRequests.valueAt(i));
|
}
|
pw.decreaseIndent();
|
}
|
|
@Override
|
public String toString() {
|
StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
|
.append(mSerialNumber).append(", ScoreFilter=")
|
.append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
|
.append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
|
.append("}");
|
return sb.toString();
|
}
|
}
|