| package com.android.clockwork.bluetooth; |
| |
| import android.annotation.AnyThread; |
| import android.annotation.MainThread; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.WorkerThread; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.IBluetooth; |
| import android.bluetooth.IBluetoothManagerCallback; |
| import android.content.Context; |
| import android.os.AsyncTask; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.os.ParcelUuid; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.util.Log; |
| import com.android.clockwork.bluetooth.proxy.ProxyServiceManager; |
| import com.android.clockwork.bluetooth.proxy.WearProxyConstants.Reason; |
| import com.android.clockwork.common.DebugAssert; |
| import com.android.clockwork.common.Util; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.IndentingPrintWriter; |
| import java.io.Closeable; |
| import java.lang.reflect.Method; |
| |
| /** |
| * Manages connections 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 Closeable, ProxyServiceManager.ProxyServiceCallback { |
| private static final String TAG = WearBluetoothConstants.LOG_TAG; |
| private static final int WHAT_START_SYSPROXY = 1; |
| private static final int WHAT_STOP_SYSPROXY = 2; |
| private static final int WHAT_JNI_CONNECTED = 3; |
| private static final int WHAT_JNI_DISCONNECTED = 4; |
| private static final int WHAT_CONNECTION_FAILED = 5; |
| private static final int WHAT_RESET_CONNECTION = 6; |
| |
| private static final int INVALID_NETWORK_TYPE = -1; |
| private static final int TYPE_RFCOMM = 1; |
| private static final int SEC_FLAG_ENCRYPT = 1 << 0; |
| private static final int SEC_FLAG_AUTH = 1 << 1; |
| // 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 ParcelUuid PROXY_UUID = |
| ParcelUuid.fromString("fafbdd20-83f0-4389-addf-917ac9dae5b2"); |
| |
| private static int sInstance; |
| private final int mInstance; |
| private int mStartAttempts; |
| |
| static native void classInitNative(); |
| @VisibleForTesting native boolean connectNative(int fd); |
| @VisibleForTesting native boolean disconnectNative(); |
| |
| static { |
| try { |
| System.loadLibrary("wear-bluetooth-jni"); |
| classInitNative(); |
| } catch (UnsatisfiedLinkError e) { |
| // Invoked during testing |
| Log.e(TAG, "Unable to load wear bluetooth sysproxy jni native" |
| + " libraries"); |
| } |
| } |
| |
| @NonNull private final Context mContext; |
| @NonNull private final BluetoothDevice mCompanionDevice; |
| @NonNull private final Listener mListener; |
| @NonNull private final ProxyServiceManager mProxyServiceManager; |
| |
| private final MultistageExponentialBackoff mReconnectBackoff; |
| @VisibleForTesting boolean mIsClosed; |
| |
| /** State of sysproxy module process |
| * |
| * This is a static field because the instance gets |
| * created and destroyed by the upper layer based upon |
| * specific conditions (e.g. GMS core connected or not) |
| * |
| * As each instance is created the state of the JNI sysproxy |
| * is unknown, so the state of the previous instance is kept here. |
| */ |
| @VisibleForTesting |
| static enum State { |
| SYSPROXY_DISCONNECTED, // IDLE or JNI callback |
| BLUETOOTH_SOCKET_REQUESTING, // Background thread |
| BLUETOOTH_SOCKET_RETRIEVED, |
| SYSPROXY_SOCKET_DELIVERING, // Background thread |
| SYSPROXY_SOCKET_DELIVERED, |
| SYSPROXY_CONNECTED, // JNI callback |
| SYSPROXY_DISCONNECT_REQUEST, |
| SYSPROXY_DISCONNECT_RESPONSE, |
| } |
| |
| @VisibleForTesting |
| static ProxyState mState = new ProxyState(State.SYSPROXY_DISCONNECTED); |
| |
| @VisibleForTesting |
| static class ProxyState { |
| private State mState; |
| |
| public ProxyState(final State state) { |
| mState = state; |
| } |
| |
| @MainThread |
| public boolean checkState(final State state) { |
| DebugAssert.isMainThread(); |
| return mState == state; |
| } |
| |
| @MainThread |
| public void advanceState(final int instance, final State state) { |
| DebugAssert.isMainThread(); |
| mState = state; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + instance + " ] Set new companion proxy" |
| + " state:" + mState); |
| } |
| } |
| |
| @AnyThread |
| public State current() { |
| return mState; |
| } |
| } |
| |
| /** |
| * Callback executed when the sysproxy becomes connected or disconnected |
| * |
| * This may send duplicate disconnect events, because failed reconnect |
| * attempts are indistinguishable from actual disconnects. |
| * Listeners should appropriately deduplicate these disconnect events. |
| */ |
| public interface Listener { |
| void onProxyConnectionChange(boolean isConnected, int proxyScore); |
| } |
| |
| public CompanionProxyShard( |
| @NonNull final Context context, |
| @NonNull final ProxyServiceManager proxyServiceManager, |
| @NonNull final BluetoothDevice companionDevice, |
| @NonNull final Listener listener) { |
| DebugAssert.isMainThread(); |
| |
| mContext = context; |
| mProxyServiceManager = proxyServiceManager; |
| mCompanionDevice = companionDevice; |
| mListener = listener; |
| |
| mProxyServiceManager.setCallback(this); |
| mReconnectBackoff = new MultistageExponentialBackoff(BACKOFF_BASE_INTERVAL, |
| BACKOFF_BASE_PERIOD, BACKOFF_MAX_INTERVAL); |
| |
| mInstance = sInstance++; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Created companion proxy" |
| + " shard"); |
| } |
| } |
| |
| /** Completely shuts down companion proxy network */ |
| @MainThread |
| @Override // Closable |
| public void close() { |
| DebugAssert.isMainThread(); |
| if (mIsClosed) { |
| Log.w(TAG, "CompanionProxyShard [ " + mInstance + " ] Already closed"); |
| return; |
| } |
| mProxyServiceManager.setCallback(null); |
| disconnectAndNotify(Reason.CLOSABLE); |
| // notify mListeners of our intended disconnect before setting mIsClosed to true |
| mIsClosed = true; |
| disconnectNativeInBackground(); |
| mHandler.removeMessages(WHAT_START_SYSPROXY); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Closed companion proxy shard"); |
| } |
| } |
| |
| @MainThread |
| @Override // ProxyServiceManager.ProxyServiceCallback |
| public void onStartNetwork() { |
| DebugAssert.isMainThread(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] onStartNetwork()"); |
| } |
| mHandler.sendEmptyMessage(WHAT_START_SYSPROXY); |
| } |
| |
| @MainThread |
| @Override // ProxyServiceManager.ProxyServiceCallback |
| public void onStopNetwork() { |
| DebugAssert.isMainThread(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] onStopNetwork()"); |
| } |
| mHandler.sendEmptyMessage(WHAT_STOP_SYSPROXY); |
| } |
| |
| @MainThread |
| @Override // ProxyServiceManager.ProxyServiceCallback |
| public void onUpdateNetwork(int networkScore) { |
| DebugAssert.isMainThread(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] onUpdateNetwork()"); |
| |
| } |
| } |
| |
| /** 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: |
| mStartAttempts++; |
| mHandler.removeMessages(WHAT_START_SYSPROXY); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Request to start" |
| + " companion sysproxy network"); |
| } |
| if (mState.checkState(State.SYSPROXY_CONNECTED)) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] companion proxy" |
| + " network already running set connected"); |
| } |
| mProxyServiceManager.ensureValidNetworkAgent("Already Connected"); |
| connectAndNotify(Reason.SYSPROXY_WAS_CONNECTED); |
| } else if (mState.checkState(State.SYSPROXY_DISCONNECTED)) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] companion proxy" |
| + " network starting up sysproxy module state:" |
| + mState.current()); |
| } |
| mProxyServiceManager.ensureValidNetworkAgent(Reason.START_SYSPROXY); |
| getBluetoothSocket(); |
| } else { |
| final int nextRetry = mReconnectBackoff.getNextBackoff(); |
| Log.w(TAG, "CompanionProxyShard [ " + mInstance + " ] network not" |
| + " idle/disconnected state:" + mState.current() |
| + " attempting reconnect in " + nextRetry + " seconds"); |
| mHandler.sendEmptyMessageDelayed(WHAT_START_SYSPROXY, nextRetry * 1000); |
| } |
| break; |
| case WHAT_STOP_SYSPROXY: |
| // If not closed from the upper layer, the proxy will always try maintain a |
| // connection. However, when the connectivity service decides this network |
| // is unwanted we must break down the current network agent and re-attach |
| // launch with a new network agent. |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Stop companion proxy" |
| + " network"); |
| } |
| if (mState.checkState(State.SYSPROXY_CONNECTED)) { |
| disconnectAndNotify(Reason.STOP_SYSPROXY); |
| } else { |
| Log.w(TAG, "CompanionProxyShard [ " + mInstance + " ] companion proxy" |
| + " network not connected! state:" + mState.current()); |
| } |
| break; |
| case WHAT_JNI_CONNECTED: |
| mReconnectBackoff.reset(); |
| mState.advanceState(mInstance, State.SYSPROXY_CONNECTED); |
| break; |
| case WHAT_JNI_DISCONNECTED: |
| final int status = msg.arg1; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI onDisconnect" |
| + " sysproxy closed mIsClosed:" + mIsClosed + " status:" + status); |
| } |
| if (!mIsClosed && mState.current() != State.SYSPROXY_DISCONNECTED) { |
| disconnectAndNotify(Reason.SYSPROXY_DISCONNECTED); |
| setUpRetry(); |
| } |
| mState.advanceState(mInstance, State.SYSPROXY_DISCONNECTED); |
| break; |
| case WHAT_CONNECTION_FAILED: |
| if (mState.checkState(State.SYSPROXY_CONNECTED)) { |
| Log.e(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI sysproxy failed" |
| + " state:" + mState.current()); |
| } else { |
| Log.e(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI sysproxy unable" |
| + " to connect state:" + mState.current()); |
| } |
| // fall through |
| case WHAT_RESET_CONNECTION: |
| // Take a hammer to reset everything on sysproxy side to initial state. |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Reset companion proxy" |
| + " network connection last state:" + mState.current() |
| + " isClosed:" + mIsClosed); |
| } |
| mHandler.removeMessages(WHAT_START_SYSPROXY); |
| mHandler.removeMessages(WHAT_RESET_CONNECTION); |
| disconnectNativeInBackground(); |
| setUpRetry(); |
| break; |
| } |
| } |
| }; |
| |
| private void setUpRetry() { |
| // Setup a reconnect sequence if shard has not been closed. |
| if (!mIsClosed) { |
| final int nextRetry = mReconnectBackoff.getNextBackoff(); |
| mHandler.sendEmptyMessageDelayed(WHAT_START_SYSPROXY, nextRetry * 1000); |
| Log.w(TAG, "CompanionProxyShard [ " + mInstance + " ] Proxy reset" |
| + " Attempting reconnect in " + nextRetry + " seconds"); |
| } |
| } |
| |
| /** Use binder API to directly request rfcomm socket from bluetooth module */ |
| @MainThread |
| private void getBluetoothSocket() { |
| DebugAssert.isMainThread(); |
| |
| mState.advanceState(mInstance, State.BLUETOOTH_SOCKET_REQUESTING); |
| mProxyServiceManager.setConnecting(Reason.REQUEST_BT_SOCKET); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Retrieving bluetooth network" |
| + " socket"); |
| } |
| |
| new DefaultPriorityAsyncTask<Void, Void, ParcelFileDescriptor>() { |
| @Override |
| protected ParcelFileDescriptor doInBackgroundDefaultPriority() { |
| try { |
| final IBluetooth bluetoothProxy = getBluetoothService(mInstance); |
| if (bluetoothProxy == null) { |
| Log.e(TAG, "CompanionProxyShard [ " + mInstance + " ] Unable to get binder" |
| + " proxy to IBluetooth"); |
| return null; |
| } |
| ParcelFileDescriptor parcelFd = bluetoothProxy.getSocketManager().connectSocket( |
| mCompanionDevice, |
| TYPE_RFCOMM, |
| PROXY_UUID, |
| 0 /* port */, |
| SEC_FLAG_AUTH | SEC_FLAG_ENCRYPT |
| ); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] parcelFd:" |
| + parcelFd); |
| } |
| |
| return parcelFd; |
| } catch (RemoteException e) { |
| Log.e(TAG, "CompanionProxyShard [ " + mInstance + " ] Unable to get bluetooth" |
| + " service", e); |
| return null; |
| } |
| } |
| |
| @Override |
| protected void onPostExecute(@Nullable ParcelFileDescriptor parcelFd) { |
| DebugAssert.isMainThread(); |
| |
| if (mIsClosed) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Shard closed after" |
| + " retrieving bluetooth socket"); |
| } |
| return; |
| } else if (parcelFd != null) { |
| if (!mState.checkState(State.BLUETOOTH_SOCKET_REQUESTING)) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] unexpected state" |
| + " after retrieving bluetooth network socket state:" |
| + mState.current()); |
| } |
| } else { |
| mState.advanceState(mInstance, State.BLUETOOTH_SOCKET_RETRIEVED); |
| final int fd = parcelFd.detachFd(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Retrieved" |
| + " bluetooth network socket state:" + mState.current() |
| + " parcelFd:" + parcelFd + " fd:" + fd); |
| } |
| connectNativeInBackground(fd); |
| } |
| } else { |
| Log.e(TAG, "CompanionProxyShard [ " + mInstance + " ] Unable to request" |
| + "bluetooth network socket"); |
| mHandler.sendEmptyMessage(WHAT_RESET_CONNECTION); |
| } |
| Util.close(parcelFd); |
| } |
| }.execute(); |
| } |
| |
| @MainThread |
| private void connectNativeInBackground(Integer fd) { |
| DebugAssert.isMainThread(); |
| mState.advanceState(mInstance, State.SYSPROXY_SOCKET_DELIVERING); |
| mProxyServiceManager.setConnecting(Reason.DELIVER_BT_SOCKET); |
| |
| new ConnectSocketAsyncTask() { |
| @Override |
| protected Boolean doInBackgroundDefaultPriority(Integer fileDescriptor) { |
| Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); |
| final int fd = fileDescriptor.intValue(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] connectNativeInBackground" |
| + " state:" + mState.current() + " fd:" + fd); |
| } |
| final boolean rc = connectNative(fd); |
| return new Boolean(rc); |
| } |
| |
| @Override |
| protected void onPostExecute(Boolean result) { |
| DebugAssert.isMainThread(); |
| if (mIsClosed) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] Shard closed after" |
| + "sending bluetooth socket"); |
| } |
| return; |
| } |
| if (result) { |
| if (!mState.checkState(State.SYSPROXY_SOCKET_DELIVERING)) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] unexpected state" |
| + " after delivering socket state:" + mState.current() |
| + " fd:" + fd); |
| } |
| } else { |
| mState.advanceState(mInstance, State.SYSPROXY_SOCKET_DELIVERED); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] proxy socket" |
| + " delivered state:" + mState.current() + " fd:" + fd); |
| } |
| } |
| } else { |
| Log.w(TAG, "CompanionProxyShard [ " + mInstance + " ] Unable to deliver socket" |
| + " to sysproxy module"); |
| mHandler.sendEmptyMessage(WHAT_RESET_CONNECTION); |
| } |
| } |
| }.execute(fd); |
| } |
| |
| /** This call should be idempotent to always ensure proper initial state */ |
| @MainThread |
| private void disconnectNativeInBackground() { |
| DebugAssert.isMainThread(); |
| if (mState.checkState(State.SYSPROXY_DISCONNECTED)) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI has already" |
| + " disconnected"); |
| } |
| return; |
| } |
| |
| mState.advanceState(mInstance, State.SYSPROXY_DISCONNECT_REQUEST); |
| new DefaultPriorityAsyncTask<Void, Void, Boolean>() { |
| @Override |
| protected Boolean doInBackgroundDefaultPriority() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI Disconnect request to" |
| + " sysproxy module"); |
| } |
| return disconnectNative(); |
| } |
| |
| @MainThread |
| @Override |
| protected void onPostExecute(Boolean result) { |
| DebugAssert.isMainThread(); |
| if (result) { |
| mState.advanceState(mInstance, State.SYSPROXY_DISCONNECT_RESPONSE); |
| } else { |
| mState.advanceState(mInstance, State.SYSPROXY_DISCONNECTED); |
| } |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI Disconnect response" |
| + " result:" + result + " mIsClosed:" + mIsClosed); |
| } |
| } |
| }.execute(); |
| } |
| |
| /** |
| * This method is called from JNI in a background thread when the companion proxy |
| * network state changes on the phone. |
| */ |
| @WorkerThread |
| protected void onActiveNetworkState(final int networkType, final boolean isMetered) { |
| if (mIsClosed) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI onActiveNetworkState" |
| + " shard closed...bailing"); |
| } |
| return; |
| } |
| |
| if (networkType == INVALID_NETWORK_TYPE) { |
| mHandler.sendEmptyMessage(WHAT_CONNECTION_FAILED); |
| } else { |
| connectAndNotify(Reason.SYSPROXY_CONNECTED); |
| mProxyServiceManager.setMetered(isMetered); |
| mHandler.sendEmptyMessage(WHAT_JNI_CONNECTED); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CompanionProxyShard [ " + mInstance + " ] JNI sysproxy process complete" |
| + " state:" + mState.current() + " networkType:" + networkType + " metered:" |
| + isMetered); |
| } |
| } |
| } |
| |
| /** This method is called from JNI in a background thread when the proxy has disconnected. */ |
| @WorkerThread |
| protected void onDisconnect(int status) { |
| mHandler.sendMessage(mHandler.obtainMessage(WHAT_JNI_DISCONNECTED, status, 0)); |
| } |
| |
| /** |
| * These methods notify connectivity service and the upper layers |
| * with the current sysproxy state. |
| */ |
| @AnyThread |
| private void connectAndNotify(final String reason) { |
| mProxyServiceManager.setConnected(reason); |
| notifyConnectionChange(true); |
| } |
| |
| @AnyThread |
| private void disconnectAndNotify(final String reason) { |
| mProxyServiceManager.setDisconnected(reason); |
| notifyConnectionChange(false); |
| } |
| |
| /** |
| * This method notifies mListeners about the state of the sysproxy network. |
| * |
| * NOTE: CompanionProxyShard should never call onProxyConnectionChange directly! |
| * Use the notifyConnectionChange method instead. |
| */ |
| @AnyThread |
| private void notifyConnectionChange(final boolean isConnected) { |
| if (!mIsClosed) { |
| mListener.onProxyConnectionChange(isConnected, mProxyServiceManager.getNetworkScore()); |
| } |
| } |
| |
| 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 ConnectSocketAsyncTask extends AsyncTask<Integer, Void, Boolean> { |
| @Override |
| protected Boolean doInBackground(Integer... params) { |
| Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); |
| final Integer fileDescriptor = params[0]; |
| return doInBackgroundDefaultPriority(fileDescriptor); |
| } |
| |
| protected abstract Boolean doInBackgroundDefaultPriority(Integer fd); |
| } |
| |
| /** Returns the shared instance of IBluetooth using reflection (method is package private). */ |
| private static IBluetooth getBluetoothService(final int instance) { |
| try { |
| final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| final Method getBluetoothService = adapter.getClass() |
| .getDeclaredMethod("getBluetoothService", IBluetoothManagerCallback.class); |
| getBluetoothService.setAccessible(true); |
| return (IBluetooth) getBluetoothService.invoke(adapter, new Object[] { null }); |
| } catch (Exception e) { |
| Log.e(TAG, "CompanionProxyShard [ " + instance + " ] Error retrieving IBluetooth: ", e); |
| return null; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| result.append("CompanionProxyShard: " + mInstance); |
| return result.toString(); |
| } |
| |
| public void dump(@NonNull final IndentingPrintWriter ipw) { |
| ipw.printf("Companion proxy instance:%d companion device:%s\n", mInstance, |
| mCompanionDevice); |
| ipw.increaseIndent(); |
| ipw.printPair("Current state", mState.current()); |
| ipw.printPair("Is closed", mIsClosed); |
| ipw.printPair("Start attempts", mStartAttempts); |
| ipw.printPair("Start connection scheduled", |
| mHandler.hasMessages(WHAT_START_SYSPROXY)); |
| ipw.println(); |
| ipw.decreaseIndent(); |
| mProxyServiceManager.dump(ipw); |
| } |
| } |