blob: c6f34c5455823209399e8487b86bb038878a673b [file] [log] [blame]
package com.android.clockwork.bluetooth;
import static com.android.clockwork.bluetooth.proxy.ProxyConnection.CONNECT_RESULT_CONNECTED;
import static com.android.clockwork.bluetooth.proxy.ProxyConnection.CONNECT_RESULT_FAILED;
import static com.android.clockwork.bluetooth.proxy.ProxyConnection.CONNECT_RESULT_TIMEOUT;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetooth;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import com.android.clockwork.bluetooth.proxy.ProxyConnection;
import com.android.clockwork.bluetooth.proxy.ProxyConnectionV1;
import com.android.clockwork.bluetooth.proxy.ProxyConnectionV2;
import com.android.clockwork.bluetooth.proxy.ProxyNetworkAgent;
import com.android.clockwork.bluetooth.proxy.ProxyServiceConfig;
import com.android.clockwork.bluetooth.proxy.ProxyServiceHelper;
import com.android.clockwork.bluetooth.proxy.WearProxyConstants.Reason;
import com.android.clockwork.common.DebugAssert;
import com.android.clockwork.common.LogUtil;
import com.android.clockwork.common.Util;
import com.android.clockwork.common.WearBluetoothSettings;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.List;
/**
* Manages connection to the companion sysproxy network
*
* This class handles connecting to the remote device using the
* bluetooth network and configuring the sysproxy to setup the
* proper network to allow IP traffic to be utilized by Android.
*
* Steps to connect to the companion sysproxy.
*
* 1. Get a bluetooth rfcomm socket.
* This will actually establish a bluetooth connection from the device to the companion.
* 2. Pass this rfcomm socket to the sysproxy module.
* The sysproxy module will formulate the necessary network configuration to allow
* IP traffic to flow over the bluetooth socket connection.
* 3. Get acknowledgement that the sysproxy module initialized.
* This may or may not be completed successfully as indicated by the jni callback
* indicating connection or failure.
*/
public class CompanionProxyShard implements ProxyConnection.Listener {
private static final String TAG = WearBluetoothSettings.LOG_TAG;
private static final int WHAT_START_SYSPROXY = 1;
private static final int WHAT_JNI_ACTIVE_NETWORK_STATE = 2;
private static final int WHAT_JNI_DISCONNECTED = 3;
private static final int WHAT_RESET_CONNECTION = 4;
// Relative unitless network retry values
private static final int BACKOFF_BASE_INTERVAL = 2;
private static final int BACKOFF_BASE_PERIOD = 5;
private static final int BACKOFF_MAX_INTERVAL = 300;
private static final int IS_NOT_METERED = 0;
private static final int IS_METERED = 1;
private static final boolean IS_CONNECTED = true;
private static final boolean IS_DISCONNECTED = !IS_CONNECTED;
private static final boolean PHONE_WITH_INTERNET = true;
private static final boolean PHONE_NO_INTERNET = !PHONE_WITH_INTERNET;
@VisibleForTesting int mStartAttempts;
@ProxyConnection.NetworkType
@VisibleForTesting int mNetworkType;
private boolean mIsMetered;
private boolean mIsConnected;
private boolean mPhoneNoInternet;
private ProxyServiceConfig mProxyServiceConfig = new ProxyServiceConfig();
private enum ClientState {IDLE, CONNECTING, CONNECTED, DISCONNECTING}
private ClientState mClientState = ClientState.IDLE;
/** The sysproxy network state
*
* The sysproxy network may be in one of the following states:
*
* 1. Disconnected from phone.
* There is no bluetooth rfcomm socket connected to the phone.
* 2. Connected with Internet access via phone.
* There is a valid rfcomm socket connected to phone and the phone has a default
* network that had validated access to the Internet.
* 3. Connected without Internet access via phone.
* There is a vaid rfcomm socket connected to phone but the phone has no validated
* network to the Internet.
*/
private enum SysproxyNetworkState {
DISCONNECTED,
CONNECTED_NO_INTERNET,
CONNECTED_WITH_INTERNET,
}
@NonNull private final Context mContext;
@NonNull private final ProxyServiceHelper mProxyServiceHelper;
@NonNull private final CompanionTracker mCompanionTracker;
private Listener mListener;
private final MultistageExponentialBackoff mReconnectBackoff;
@VisibleForTesting boolean mIsStarted;
private volatile ProxyConnection mProxyConnection;
public interface Listener {
/**
* Callback executed when the sysproxy connection changes state.
*
* This may send duplicate disconnect events, because failed reconnect
* attempts are indistinguishable from actual disconnects.
* Listeners should appropriately deduplicate these disconnect events.
*/
void onProxyConnectionChange(boolean isConnected, int proxyScore, boolean phoneNoInternet);
/**
* Callback executed when the sysproxy has incoming or outgoing data on the BLE L2CAP
* socket.
*/
void onProxyBleData();
/**
* Callback executed when the sysproxy fails to connect to its counterpart.
*/
void onProxyConnectFailed(ProxyServiceConfig failedConfig);
}
public CompanionProxyShard(
@NonNull final Context context,
@NonNull final CompanionTracker companionTracker,
final boolean isLocalEdition) {
DebugAssert.isMainThread();
mContext = context;
mCompanionTracker = companionTracker;
mProxyServiceHelper = new ProxyServiceHelper(
mContext, new NetworkCapabilities.Builder(), isLocalEdition);
mReconnectBackoff = new MultistageExponentialBackoff(BACKOFF_BASE_INTERVAL,
BACKOFF_BASE_PERIOD, BACKOFF_MAX_INTERVAL);
maybeLogDebug("Created companion proxy shard");
}
/**
* Completely shuts down companion proxy network.
*
* Disconnects sysproxy client, effectively closing BT socket and detach current listener from
* sysproxy network updates.
*/
@MainThread
public void stop() {
DebugAssert.isMainThread();
if (!mIsStarted) {
Log.w(TAG, logInstance() + "Already stopped");
return;
}
mReconnectBackoff.reset();
updateAndNotify(SysproxyNetworkState.DISCONNECTED, Reason.CLOSABLE);
// notify mListener of our intended disconnect before setting mIsStarted to false
mIsStarted = false;
disconnectNativeInBackground();
mHandler.removeMessages(WHAT_START_SYSPROXY);
maybeLogDebug("Stopped companion proxy shard");
mListener = null;
}
@MainThread
private void restart() {
if (!mIsStarted) {
Log.w(TAG, logInstance() + "restart: Already stopped");
return;
}
mReconnectBackoff.reset();
disconnectNativeInBackground();
mHandler.removeMessages(WHAT_START_SYSPROXY);
mHandler.sendEmptyMessage(WHAT_START_SYSPROXY);
}
/**
* Start sysproxy network connecting to RFCOMM socket and sysproxy native service.
*
* While sysproxy is connected {@code listener} is notified about underling network
* status changes.
*/
@MainThread
public void startNetwork(int networkScore, List<InetAddress> dnsServers,
ProxyServiceConfig serviceConfig, Listener listener) {
DebugAssert.isMainThread();
LogUtil.logD(TAG, "startNetwork(%s, %s)", networkScore, serviceConfig);
if (!serviceConfig.equals(mProxyServiceConfig)) {
stop();
mProxyServiceConfig = serviceConfig;
}
mIsStarted = true;
// TODO(b/161549960): move to list of listeners, add/remove listener API, as we refactor
// and directly use this class from mediator
if (mListener != null && mListener != listener) {
Log.e(TAG, logInstance() + "Replacing existing NON-NULL listener");
}
mListener = listener;
mProxyServiceHelper.setCompanionName(mCompanionTracker.getCompanionName());
mProxyServiceHelper.setDnsServers(dnsServers);
mProxyServiceHelper.setNetworkScore(networkScore);
mHandler.sendEmptyMessage(WHAT_START_SYSPROXY);
}
@MainThread
public boolean isStarted() {
return mIsStarted;
}
@MainThread
public void updateNetwork(final int networkScore) {
DebugAssert.isMainThread();
mProxyServiceHelper.setNetworkScore(networkScore);
notifyConnectionChange(mIsConnected, mPhoneNoInternet);
}
/** Update DnsServers used by proxy. */
@MainThread
public void updateNetwork(final List<InetAddress> dnsServers) {
DebugAssert.isMainThread();
mProxyServiceHelper.setDnsServers(dnsServers);
}
/** Serialize state change requests here */
@VisibleForTesting
final Handler mHandler = new Handler() {
@MainThread
@Override
public void handleMessage(Message msg) {
DebugAssert.isMainThread();
switch (msg.what) {
case WHAT_START_SYSPROXY:
mHandler.removeMessages(WHAT_START_SYSPROXY);
if (!mIsStarted) {
maybeLogDebug("start sysproxy but shard stopped...will bail");
return;
}
if (mClientState == ClientState.DISCONNECTING) {
maybeLogDebug("waiting for sysproxy to disconnect...will retry");
mHandler.sendEmptyMessage(WHAT_RESET_CONNECTION);
return;
}
mStartAttempts++;
if (connectedWithInternet()) {
maybeLogDebug("start sysproxy already running set connected");
updateAndNotify(SysproxyNetworkState.CONNECTED_WITH_INTERNET,
Reason.SYSPROXY_WAS_CONNECTED);
} else if (connectedNoInternet()) {
maybeLogDebug("start sysproxy already running but with no internet access");
updateAndNotify(SysproxyNetworkState.CONNECTED_NO_INTERNET,
Reason.SYSPROXY_NO_INTERNET);
} else {
maybeLogDebug("start up new sysproxy connection");
connectSysproxyInBackground();
}
break;
case WHAT_JNI_ACTIVE_NETWORK_STATE:
mNetworkType = msg.arg1;
mIsMetered = msg.arg2 == IS_METERED;
updateClientState(ClientState.CONNECTED);
mReconnectBackoff.reset();
if (!mIsStarted) {
maybeLogDebug("JNI onActiveNetworkState shard stopped...will bail");
return;
}
if (connectedWithInternet()) {
updateAndNotify(SysproxyNetworkState.CONNECTED_WITH_INTERNET,
Reason.SYSPROXY_CONNECTED);
mProxyServiceHelper.setMetered(mIsMetered);
} else if (connectedNoInternet()) {
updateAndNotify(SysproxyNetworkState.CONNECTED_NO_INTERNET,
Reason.SYSPROXY_NO_INTERNET);
}
maybeLogDebug("JNI sysproxy process complete networkType:" + mNetworkType
+ " metered:" + mIsMetered);
break;
case WHAT_JNI_DISCONNECTED:
final int status = msg.arg1;
updateClientState(ClientState.IDLE);
maybeLogDebug("JNI onDisconnect isStarted:" + mIsStarted + " status:" + status);
updateAndNotify(SysproxyNetworkState.DISCONNECTED,
Reason.SYSPROXY_DISCONNECTED);
setUpRetryIfNotStopped();
break;
case WHAT_RESET_CONNECTION:
// Take a hammer to reset everything on sysproxy side to initial state.
maybeLogDebug(
"Reset companion proxy network connection isStarted:" + mIsStarted);
mHandler.removeMessages(WHAT_START_SYSPROXY);
mHandler.removeMessages(WHAT_RESET_CONNECTION);
disconnectNativeInBackground();
setUpRetryIfNotStopped();
break;
}
}
};
private void setUpRetryIfNotStopped() {
if (mIsStarted) {
final int nextRetry = mReconnectBackoff.getNextBackoff();
mHandler.sendEmptyMessageDelayed(WHAT_START_SYSPROXY, nextRetry * 1000);
Log.w(TAG, logInstance() + "Attempting reconnect in " + nextRetry + " seconds");
}
}
@MainThread
private void updateAndNotify(final SysproxyNetworkState state, final String reason) {
DebugAssert.isMainThread();
ProxyConnection proxyConnection = mProxyConnection;
if (state == SysproxyNetworkState.CONNECTED_WITH_INTERNET && proxyConnection != null) {
mProxyServiceHelper.startNetworkSession(
reason,
proxyConnection.getInterfaceName(),
proxyConnection.getMtu(),
/**
* Called when the current network agent is no longer wanted
* by {@link ConnectivityService}. Try to restart the network.
*/
new ProxyNetworkAgent.Listener() {
@Override
public void onNetworkAgentUnwanted(int netId) {
Log.d(TAG, logInstance() + "Network agent unwanted netId:" + netId);
mHandler.sendEmptyMessage(WHAT_START_SYSPROXY);
}
});
notifyConnectionChange(IS_CONNECTED, PHONE_WITH_INTERNET);
} else if (state == SysproxyNetworkState.CONNECTED_NO_INTERNET) {
mProxyServiceHelper.stopNetworkSession(reason);
notifyConnectionChange(IS_CONNECTED, PHONE_NO_INTERNET);
} else {
mProxyServiceHelper.stopNetworkSession(reason);
notifyConnectionChange(IS_DISCONNECTED);
}
}
/**
* Request an rfcomm or l2cap socket to the companion device.
*
* Connect to the companion device with a bluetooth rfcomm or l2cap socket.
* The integer filedescriptor portion of the {@link ParcelFileDescriptor}
* is used to pass to the sysproxy native code via
* {@link CompanionProxyShard#connectNativeInBackground}
*
* Failures in any of these steps enter into a delayed retry mode.
*/
@MainThread
private void connectSysproxyInBackground() {
DebugAssert.isMainThread();
if (mClientState != ClientState.IDLE) {
return;
}
updateClientState(ClientState.CONNECTING);
maybeLogDebug("Retrieving bluetooth network socket");
new DefaultPriorityAsyncTask<Void, Void, ParcelFileDescriptor>() {
@Override
protected ParcelFileDescriptor doInBackgroundDefaultPriority() {
try {
final IBluetooth bluetoothProxy = getBluetoothService();
if (bluetoothProxy == null) {
Log.e(TAG, logInstance() + "Unable to get binder proxy to IBluetooth");
return null;
}
ParcelFileDescriptor parcelFd = bluetoothProxy.getSocketManager().connectSocket(
mCompanionTracker.getCompanion(),
mProxyServiceConfig.getAidlConnectionType(),
mProxyServiceConfig.serviceUuid,
mProxyServiceConfig.connectionPort,
mProxyServiceConfig.getAidlConnectionFlags());
maybeLogDebug("Connect with config=" + mProxyServiceConfig
+ " parcelFd=" + fdToString(parcelFd));
if (mProxyServiceConfig.isIos()) {
// Only needed on iOS. Monitors traffic on the BT socket and pings the
// companion if needed.
return createBluetoothSocketMonitor(() -> {
mHandler.post(() -> {
if (mListener != null) {
mListener.onProxyBleData();
}
});
}).start(parcelFd);
} else {
return parcelFd;
}
} catch (RemoteException e) {
Log.e(TAG, logInstance() + "Unable to get bluetooth service", e);
return null;
}
}
@Override
protected void onPostExecute(@Nullable ParcelFileDescriptor parcelFd) {
DebugAssert.isMainThread();
if (!mIsStarted) {
maybeLogDebug("Shard stopped after retrieving bluetooth socket");
Util.close(parcelFd);
updateClientState(ClientState.IDLE);
return;
}
if (parcelFd != null) {
maybeLogDebug("Retrieved bluetooth network socket parcelFd:"
+ fdToString(parcelFd));
connectNativeInBackground(parcelFd);
} else {
Log.e(TAG, logInstance() + "Unable to request bluetooth network socket");
updateClientState(ClientState.IDLE);
if (mListener != null) {
mListener.onProxyConnectFailed(mProxyServiceConfig);
}
mHandler.sendEmptyMessage(WHAT_RESET_CONNECTION);
}
}
}.execute();
}
@VisibleForTesting
protected BluetoothSocketMonitor createBluetoothSocketMonitor(
BluetoothSocketMonitor.Listener listener) {
return new BluetoothSocketMonitor(listener);
}
/**
* Pass connected socket to sysproxy module.
*
* Hand off a connected socket to the native sysproxy code to
* provide bidirectional network connectivity for the system.
*/
@MainThread
private void connectNativeInBackground(ParcelFileDescriptor parcelFd) {
DebugAssert.isMainThread();
new PassSocketAsyncTask() {
@Override
protected Integer doInBackgroundDefaultPriority(ParcelFileDescriptor fileDescriptor) {
maybeLogDebug("connectNativeInBackground state: " + mClientState
+ " fd:" + fdToString(fileDescriptor)
+ " config:" + mProxyServiceConfig);
if (mProxyConnection != null) {
Log.w(TAG, logInstance()
+ "connectNativeInBackground already has an open connection");
mProxyConnection.disconnect();
mProxyConnection = null;
}
if (mProxyServiceConfig.type == ProxyServiceConfig.Type.V2_ANDROID) {
mProxyConnection = new ProxyConnectionV2(CompanionProxyShard.this);
} else {
mProxyConnection = new ProxyConnectionV1(
mProxyServiceConfig.getJniSysproxyVersion(), CompanionProxyShard.this);
}
// for sysproxy v1.x it's possible to recieve onActiveNetworkState call before
// returning from mProxyConnection.connect(...), see b/257392974
return handleConnectResultInBackground(mProxyConnection.connect(fileDescriptor));
}
@Override
protected void onPostExecute(Integer result) {
handleConnectResultInMain(result);
}
}.execute(parcelFd);
}
@MainThread
private void continueSysproxyConnect() {
DebugAssert.isMainThread();
if (!mIsStarted) {
maybeLogDebug("Shard stopped while continuing to connect sysproxy");
return;
}
new DefaultPriorityAsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackgroundDefaultPriority() {
if (mProxyConnection == null) {
maybeLogDebug("no connection to continue sysproxy connect");
return CONNECT_RESULT_FAILED;
}
maybeLogDebug("continue sysproxy connect after timeout.");
return handleConnectResultInBackground(mProxyConnection.continueConnect());
}
@Override
protected void onPostExecute(Integer result) {
handleConnectResultInMain(result);
}
}.execute();
}
private Integer handleConnectResultInBackground(int result) {
if (result == CONNECT_RESULT_FAILED) {
mProxyConnection.disconnect();
mProxyConnection = null;
}
return result;
}
private void handleConnectResultInMain(Integer result) {
DebugAssert.isMainThread();
if (!mIsStarted) {
maybeLogDebug("Shard stopped after sending bluetooth socket");
return;
}
if (mClientState != ClientState.CONNECTING) {
maybeLogDebug("Not in connecting state " + mClientState
+ ", cannot continue to connect sysproxy");
return;
}
ProxyConnection proxyConnection = mProxyConnection;
if (result == CONNECT_RESULT_CONNECTED && proxyConnection != null) {
Log.i(TAG, logInstance() + "ProxyConnection successfully connected, if="
+ proxyConnection.getInterfaceName() + ", mtu=" + proxyConnection.getMtu()
+ ", config=" + mProxyServiceConfig);
return;
}
if (result == CONNECT_RESULT_TIMEOUT) {
continueSysproxyConnect();
return;
}
Log.w(TAG, logInstance() + "Unable to establish sysproxy connection");
updateClientState(ClientState.IDLE);
if (mListener != null) {
mListener.onProxyConnectFailed(mProxyServiceConfig);
}
mHandler.sendEmptyMessage(WHAT_RESET_CONNECTION);
}
/**
* Disconnect the current sysproxy network session.
*
* Inform the native sysproxy module to teardown the current sysproxy session.
*/
@MainThread
private void disconnectNativeInBackground() {
DebugAssert.isMainThread();
if (mClientState == ClientState.IDLE) {
maybeLogDebug("JNI has already disconnected");
updateClientState(ClientState.IDLE);
return;
}
updateClientState(ClientState.DISCONNECTING);
new DefaultPriorityAsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackgroundDefaultPriority() {
if (mProxyConnection == null) {
return false; // Failed to talk to local sysproxy.
}
maybeLogDebug("JNI Disconnect request to sysproxy module");
if (!mProxyConnection.disconnect()) {
mProxyConnection = null;
return false; // Failed to talk to local sysproxy.
}
return true;
}
@MainThread
@Override
protected void onPostExecute(Boolean result) {
DebugAssert.isMainThread();
LogUtil.logD(TAG, "JNI Disconnect result: %s clientState: %s isStarted: %s",
result, mClientState, mIsStarted);
if (!result) {
// Failed to talk over existing client socket: no connection exist now.
// "False" result signifies successful disconnect() request.
updateClientState(ClientState.IDLE);
}
}
}.execute();
}
/**
* This method is called from JNI in a background thread when the companion proxy
* network state changes on the phone.
*/
@WorkerThread
@Override
public void onActiveNetworkState(@ProxyConnection.NetworkType final int networkType,
final boolean isMetered) {
mHandler.sendMessage(mHandler.obtainMessage(WHAT_JNI_ACTIVE_NETWORK_STATE, networkType,
isMetered ? IS_METERED : IS_NOT_METERED));
}
/** This method is called from JNI in a background thread when the proxy has disconnected. */
@WorkerThread
@Override
public void onDisconnect(final int status, final String reason) {
mHandler.sendMessage(mHandler.obtainMessage(WHAT_JNI_DISCONNECTED, status, 0));
}
/**
* This method notifies the listener about the state of the sysproxy network.
*
* NOTE: CompanionProxyShard should never call onProxyConnectionChange directly!
* Use the notifyConnectionChange method instead.
*/
@MainThread
private void notifyConnectionChange(final boolean isConnected) {
DebugAssert.isMainThread();
notifyConnectionChange(isConnected, false);
}
@MainThread
private void notifyConnectionChange(final boolean isConnected, final boolean phoneNoInternet) {
DebugAssert.isMainThread();
mIsConnected = isConnected;
mPhoneNoInternet = phoneNoInternet;
doNotifyConnectionChange(mProxyServiceHelper.getNetworkScore());
}
@MainThread
private void doNotifyConnectionChange(final int networkScore) {
DebugAssert.isMainThread();
if (mIsStarted && mListener != null) {
mListener.onProxyConnectionChange(mIsConnected, networkScore, mPhoneNoInternet);
}
}
private boolean connectedWithInternet() {
return mClientState == ClientState.CONNECTED
&& mNetworkType != ConnectivityManager.TYPE_NONE;
}
private boolean connectedNoInternet() {
return mClientState == ClientState.CONNECTED
&& mNetworkType == ConnectivityManager.TYPE_NONE;
}
private abstract static class DefaultPriorityAsyncTask<Params, Progress, Result>
extends AsyncTask<Params, Progress, Result> {
@Override
protected Result doInBackground(Params... params) {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
return doInBackgroundDefaultPriority();
}
protected abstract Result doInBackgroundDefaultPriority();
}
private abstract static class PassSocketAsyncTask extends AsyncTask<ParcelFileDescriptor, Void, Integer> {
@Override
protected Integer doInBackground(ParcelFileDescriptor... params) {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
final ParcelFileDescriptor fileDescriptor = params[0];
return doInBackgroundDefaultPriority(fileDescriptor);
}
protected abstract Integer doInBackgroundDefaultPriority(ParcelFileDescriptor fd);
}
/** Returns the shared instance of IBluetooth using reflection (method is package private). */
private static IBluetooth getBluetoothService() {
try {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
final Method getBluetoothService = adapter.getClass()
.getDeclaredMethod("getBluetoothService");
getBluetoothService.setAccessible(true);
return (IBluetooth) getBluetoothService.invoke(adapter);
} catch (Exception e) {
Log.e(TAG, logInstance() + "CompanionProxyShard Error retrieving IBluetooth: ", e);
return null;
}
}
private void updateClientState(ClientState newState) {
if (mClientState != newState) {
LogUtil.logD(TAG, "updateClientState %s -> %s", mClientState, newState);
mClientState = newState;
}
}
private static String fdToString(ParcelFileDescriptor fileDescriptor) {
if (fileDescriptor == null) {
return "null";
}
try {
return Integer.toString(fileDescriptor.getFd());
} catch (IllegalStateException e) {
return "closed";
}
}
private void maybeLogDebug(final String msg) {
LogUtil.logD(TAG, logInstance() + msg);
}
private static String logInstance() {
return "[CompanionProxyShard] ";
}
public void dump(@NonNull final IndentingPrintWriter ipw) {
ipw.printf("Companion proxy [ %s ] %s",
mCompanionTracker.getCompanion(), mCompanionTracker.getCompanionName());
ipw.println();
ipw.increaseIndent();
ipw.printPair("isStarted", mIsStarted);
ipw.printPair("Start attempts", mStartAttempts);
ipw.printPair("Start connection scheduled", mHandler.hasMessages(WHAT_START_SYSPROXY));
ipw.printPair("isMetered", mIsMetered);
ipw.printPair("clientState", mClientState);
ipw.printPair("config", mProxyServiceConfig);
ipw.println();
mProxyServiceHelper.dump(ipw);
ProxyConnection proxyConnection = mProxyConnection;
if (proxyConnection != null) {
proxyConnection.dump(ipw);
}
ipw.decreaseIndent();
}
}