blob: 5537c99e02b7a6f1ef92949c5b477ad6862bc69a [file] [log] [blame]
package com.android.clockwork.bluetooth.proxy;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_SUBTYPE_ID;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_SUBTYPE_NAME;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_TYPE_NAME;
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Looper;
import android.util.Log;
import com.android.clockwork.common.DebugAssert;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* {@link NetworkAgent} that represents bluetooth companion proxy networks.
*
* Interacts with {@link ConnectivityService} to provide proxy network connectivity
* over bluetooth. {@link ProxyNetworkAgent} provides the container and manipulation
* methods for all network agents used for sysproxy.
*
*/
public class ProxyNetworkAgent {
private static final String TAG = WearProxyConstants.LOG_TAG;
private static final String NETWORK_AGENT_NAME = "CompanionProxyAgent";
private static final boolean ALWAYS_CREATE_AGENT = true;
private static final boolean MAYBE_RECYCLE_AGENT = false;
private final Context mContext;
private final NetworkCapabilities mCapabilities;
private final ProxyLinkProperties mProxyLinkProperties;
@VisibleForTesting
@Nullable NetworkAgent mCurrentNetworkAgent;
private int mCurrentNetworkScore;
@VisibleForTesting
final HashMap<NetworkAgent, NetworkInfo> mNetworkAgents
= new HashMap<NetworkAgent, NetworkInfo>();
/** Create a idle proxy network info object */
private static NetworkInfo IDLE_NETWORK;
static {
try {
IDLE_NETWORK = new NetworkInfo(ConnectivityManager.TYPE_PROXY, PROXY_NETWORK_SUBTYPE_ID,
PROXY_NETWORK_TYPE_NAME, PROXY_NETWORK_SUBTYPE_NAME);
} catch (java.lang.NullPointerException e) {
Log.e(TAG, "Expected testing failure: " + e);
}
}
/**
* Callback executed when Connectivity Service deems a network agent unwanted()
*/
public interface Listener {
public void onNetworkAgentUnwanted(int netId);
}
protected ProxyNetworkAgent(
@NonNull final Context context,
@NonNull final NetworkCapabilities capabilities,
@NonNull final ProxyLinkProperties proxyLinkProperties) {
mContext = context;
mCapabilities = capabilities;
mProxyLinkProperties = proxyLinkProperties;
}
@MainThread
protected void sendCapabilities(final NetworkCapabilities capabilities) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
mCurrentNetworkAgent.sendNetworkCapabilities(capabilities);
} else {
Log.w(TAG, "Send capabilities with no network agent");
}
}
@MainThread
protected void setNetworkScore(final int networkScore) {
DebugAssert.isMainThread();
if (mCurrentNetworkScore != networkScore) {
mCurrentNetworkScore = networkScore;
if (mCurrentNetworkAgent != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Set network score for current network agent:"
+ mCurrentNetworkAgent.netId + " score:" + mCurrentNetworkScore);
}
mCurrentNetworkAgent.sendNetworkScore(mCurrentNetworkScore);
} else {
Log.d(TAG, "Setting network score with future network agent");
}
}
}
@MainThread
protected int getNetworkScore() {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
return mCurrentNetworkScore;
} else {
return 0;
}
}
@MainThread
protected void maybeSetUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
@Nullable final Listener listener) {
DebugAssert.isMainThread();
doSetUpNetworkAgent(reason, companionName, mCurrentNetworkScore, listener,
ProxyNetworkAgent.MAYBE_RECYCLE_AGENT);
}
@MainThread
protected void setUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
@Nullable final Listener listener) {
DebugAssert.isMainThread();
doSetUpNetworkAgent(reason, companionName, mCurrentNetworkScore, listener,
ProxyNetworkAgent.ALWAYS_CREATE_AGENT);
}
/**
* Create or recycle network agent
*
* We want to re-use the existing network agent, if available, as we only want
* a single network agent session active at any given time.
*
* We want to create a new one during initial start up, or if the previous network
* agent becomes disconnected.
*
* The {@link NetworkAgent} constructor connects that objects handler with
* {@link ConnectiviyService} in order to provide proxy network access.
*
* If there are no clients who want this network this agent will be torn down
* and unwanted() will be called. This will in turn propogate a second callback
* to the creater of {@link ProxyNetworkAgent}.
*/
private void doSetUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
final int networkScore,
@Nullable final Listener listener,
final boolean forceNewAgent) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
if (forceNewAgent) {
Log.w(TAG, "Network updated and overwriting current network agent since"
+ " one already existed ... previous agent:" + mCurrentNetworkAgent.netId);
} else {
Log.w(TAG, "Network updated and re-using existing network agent:"
+ mCurrentNetworkAgent.netId);
return;
}
}
final NetworkInfo networkInfo = new NetworkInfo(IDLE_NETWORK);
networkInfo.setIsAvailable(true);
networkInfo.setDetailedState(DetailedState.CONNECTING, reason, companionName);
mCurrentNetworkScore = networkScore;
mCurrentNetworkAgent = new NetworkAgent(
Looper.getMainLooper(),
mContext,
NETWORK_AGENT_NAME,
networkInfo,
mCapabilities,
mProxyLinkProperties.getLinkProperties(),
networkScore) {
@Override
protected void unwanted() {
DebugAssert.isMainThread();
if (listener != null) {
listener.onNetworkAgentUnwanted(this.netId);
}
tearDownNetworkAgent(this);
}
};
mNetworkAgents.put(mCurrentNetworkAgent, networkInfo);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Created network agent:" + mCurrentNetworkAgent.netId);
}
}
@MainThread
private void tearDownNetworkAgent(
@NonNull final NetworkAgent unwantedNetworkAgent) {
DebugAssert.isMainThread();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Proxy agent is not longer needed"
+ "...tearing down network agent:"
+ unwantedNetworkAgent.netId);
}
final NetworkInfo networkInfo = mNetworkAgents.get(unwantedNetworkAgent);
if (networkInfo == null) {
Log.e(TAG, "Unable to find unwanted network agent in map"
+ " network agent:" + unwantedNetworkAgent.netId);
return;
}
mNetworkAgents.remove(unwantedNetworkAgent);
if (unwantedNetworkAgent == mCurrentNetworkAgent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "disconnected unwanted current network agent:"
+ mCurrentNetworkAgent.netId);
}
mCurrentNetworkAgent = null;
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "unwanted network agent already torn down"
+ " unwanted:" + unwantedNetworkAgent.netId + " current:"
+ (mCurrentNetworkAgent == null ? "none" : mCurrentNetworkAgent.netId));
}
}
}
@MainThread
protected void invalidateCurrentNetworkAgent() {
DebugAssert.isMainThread();
mCurrentNetworkAgent = null;
}
@MainThread
protected void setConnected(@Nullable final String reason,
@Nullable final String companionName) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent == null) {
Log.w(TAG, "Send network info with no network agent reason:"
+ reason);
}
setCurrentNetworkInfo(DetailedState.CONNECTED, reason, companionName);
}
@MainThread
protected void setDisconnected(@Nullable final String reason,
@Nullable final String companionName) {
DebugAssert.isMainThread();
setCurrentNetworkInfo(DetailedState.DISCONNECTED, reason, companionName);
}
@MainThread
private void setCurrentNetworkInfo(DetailedState detailedState, String reason,
String extraInfo) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
final NetworkInfo networkInfo = mNetworkAgents.get(mCurrentNetworkAgent);
networkInfo.setDetailedState(detailedState, reason, extraInfo);
mNetworkAgents.put(mCurrentNetworkAgent, networkInfo);
mCurrentNetworkAgent.sendNetworkInfo(networkInfo);
}
}
@AnyThread
public void dump(@NonNull final IndentingPrintWriter ipw) {
ipw.printPair("Network agent id", (mCurrentNetworkAgent == null)
? "none" : mCurrentNetworkAgent.netId);
ipw.printPair("score", (mCurrentNetworkAgent == null) ? 0 : mCurrentNetworkScore);
ipw.println();
ipw.increaseIndent();
for (Map.Entry<NetworkAgent, NetworkInfo> entry : mNetworkAgents.entrySet()) {
ipw.printPair(entry.getKey().toString(), entry.getValue());
}
ipw.decreaseIndent();
ipw.println();
}
}