1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.usb.handler; 18 19 import android.annotation.Nullable; 20 import android.annotation.WorkerThread; 21 import android.car.AoapService; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.hardware.usb.UsbDevice; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.Messenger; 34 import android.os.RemoteException; 35 import android.util.Log; 36 import android.util.SparseArray; 37 38 import java.lang.ref.WeakReference; 39 import java.util.HashMap; 40 import java.util.concurrent.CompletableFuture; 41 import java.util.concurrent.ExecutionException; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.TimeoutException; 44 45 /** Manages connections to {@link android.car.AoapService} (AOAP handler apps). */ 46 public class AoapServiceManager { 47 private static final String TAG = AoapServiceManager.class.getSimpleName(); 48 49 // Keep in sync with android.car.AoapService.java. 50 private static final String KEY_DEVICE = "usb-device"; 51 private static final String KEY_RESULT = "result"; 52 private static final int MSG_CAN_SWITCH_TO_AOAP = 3; 53 private static final int MSG_CAN_SWITCH_TO_AOAP_RESPONSE = 4; 54 private static final int MSG_NEW_DEVICE_ATTACHED = 1; 55 private static final int MSG_NEW_DEVICE_ATTACHED_RESPONSE = 2; 56 57 private static final int MSG_DISCONNECT = 1; 58 private static final int DISCONNECT_DELAY_MS = 30000; 59 private static final int INVOCATION_TIMEOUT_MS = 20000; 60 61 private final HashMap<ComponentName, AoapServiceConnection> mConnections = new HashMap<>(); 62 private Context mContext; 63 private final Object mLock = new Object(); 64 private final HandlerThread mHandlerThread; 65 private final Handler mHandler; 66 AoapServiceManager(Context context)67 public AoapServiceManager(Context context) { 68 mContext = context; 69 70 mHandlerThread = new HandlerThread(TAG); 71 mHandlerThread.start(); 72 73 mHandler = new Handler(mHandlerThread.getLooper()) { 74 @Override 75 public void handleMessage(Message msg) { 76 if (msg.what == MSG_DISCONNECT) { 77 removeConnection((AoapServiceConnection) msg.obj); 78 } else { 79 Log.e(TAG, "Unexpected message " + msg.what); 80 } 81 } 82 }; 83 } 84 85 /** 86 * Calls synchronously with timeout {@link #INVOCATION_TIMEOUT_MS} to the given service to check 87 * if it supports the device. 88 */ 89 @WorkerThread isDeviceSupported(UsbDevice device, ComponentName serviceName)90 public boolean isDeviceSupported(UsbDevice device, ComponentName serviceName) { 91 final AoapServiceConnection connection = getConnectionOrNull(serviceName); 92 if (connection == null) { 93 return false; 94 } 95 96 try { 97 return connection.isDeviceSupported(device) 98 .get(INVOCATION_TIMEOUT_MS, TimeUnit.MILLISECONDS); 99 } catch (ExecutionException | InterruptedException | TimeoutException e) { 100 Log.w(TAG, "Failed to get response isDeviceSupported from " + serviceName, e); 101 return false; 102 } 103 } 104 105 /** 106 * Calls synchronously with timeout {@link #INVOCATION_TIMEOUT_MS} to the given service to check 107 * if the device can be switched to AOAP mode now. 108 */ 109 @WorkerThread canSwitchDeviceToAoap(UsbDevice device, ComponentName serviceName)110 public boolean canSwitchDeviceToAoap(UsbDevice device, ComponentName serviceName) { 111 final AoapServiceConnection connection = getConnectionOrNull(serviceName); 112 if (connection == null) { 113 return false; 114 } 115 116 try { 117 return connection.canSwitchDeviceToAoap(device) 118 .get(INVOCATION_TIMEOUT_MS, TimeUnit.MILLISECONDS); 119 } catch (ExecutionException | InterruptedException | TimeoutException e) { 120 Log.w(TAG, "Failed to get response of canSwitchDeviceToAoap from " + serviceName, e); 121 return false; 122 } 123 } 124 125 @Nullable getConnectionOrNull(ComponentName name)126 private AoapServiceConnection getConnectionOrNull(ComponentName name) { 127 AoapServiceConnection connection; 128 synchronized (mLock) { 129 connection = mConnections.get(name); 130 if (connection != null) { 131 postponeServiceDisconnection(connection); 132 return connection; 133 } 134 135 connection = new AoapServiceConnection(name, this, mHandlerThread.getLooper()); 136 boolean bound = mContext.bindService( 137 createIntent(name), connection, Context.BIND_AUTO_CREATE); 138 if (bound) { 139 mConnections.put(name, connection); 140 postponeServiceDisconnection(connection); 141 } else { 142 Log.w(TAG, "Failed to bind to service " + name); 143 return null; 144 } 145 } 146 return connection; 147 } 148 postponeServiceDisconnection(AoapServiceConnection connection)149 private void postponeServiceDisconnection(AoapServiceConnection connection) { 150 mHandler.removeMessages(MSG_DISCONNECT, connection); 151 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT, connection), 152 DISCONNECT_DELAY_MS); 153 } 154 createIntent(ComponentName name)155 private static Intent createIntent(ComponentName name) { 156 Intent intent = new Intent(); 157 intent.setComponent(name); 158 return intent; 159 } 160 removeConnection(AoapServiceConnection connection)161 private void removeConnection(AoapServiceConnection connection) { 162 Log.i(TAG, "Removing connection to " + connection); 163 synchronized (mLock) { 164 mConnections.remove(connection.mComponentName); 165 if (connection.mBound) { 166 mContext.unbindService(connection); 167 connection.mBound = false; 168 } 169 } 170 } 171 172 private static class AoapServiceConnection implements ServiceConnection { 173 private Messenger mOutgoingMessenger; 174 private boolean mBound; 175 private final CompletableFuture<Void> mConnected = new CompletableFuture<>(); 176 private final SparseArray<CompletableFuture<Bundle>> mExpectedResponses = 177 new SparseArray<>(); 178 private final ComponentName mComponentName; 179 private final WeakReference<AoapServiceManager> mManagerRef; 180 private final Messenger mIncomingMessenger; 181 private final Object mLock = new Object(); 182 AoapServiceConnection(ComponentName name, AoapServiceManager manager, Looper looper)183 private AoapServiceConnection(ComponentName name, AoapServiceManager manager, 184 Looper looper) { 185 mComponentName = name; 186 mManagerRef = new WeakReference<>(manager); 187 mIncomingMessenger = new Messenger(new Handler(looper) { 188 @Override 189 public void handleMessage(Message msg) { 190 onResponse(msg); 191 } 192 }); 193 } 194 195 @Override onServiceConnected(ComponentName name, IBinder service)196 public void onServiceConnected(ComponentName name, IBinder service) { 197 if (service == null) { 198 Log.e(TAG, "Binder object was not provided on service connection to " + name); 199 return; 200 } 201 202 synchronized (mLock) { 203 mBound = true; 204 mOutgoingMessenger = new Messenger(service); 205 } 206 mConnected.complete(null); 207 } 208 209 @Override onServiceDisconnected(ComponentName name)210 public void onServiceDisconnected(ComponentName name) { 211 synchronized (mLock) { 212 mOutgoingMessenger = null; 213 mBound = false; 214 } 215 216 final AoapServiceManager mgr = mManagerRef.get(); 217 if (mgr != null) { 218 mgr.removeConnection(this); 219 } 220 } 221 onResponse(Message message)222 private void onResponse(Message message) { 223 final CompletableFuture<Bundle> response; 224 synchronized (mLock) { 225 response = mExpectedResponses.removeReturnOld(message.what); 226 } 227 if (response == null) { 228 Log.e(TAG, "Received unexpected response " + message.what + ", expected: " 229 + mExpectedResponses); 230 return; 231 } 232 233 if (message.getData() == null) { 234 throw new IllegalArgumentException("Received response msg " + message.what 235 + " without data"); 236 } 237 Log.i(TAG, "onResponse msg: " + message.what + ", data: " + message.getData()); 238 boolean res = response.complete(message.getData()); 239 if (!res) { 240 Log.w(TAG, "Failed to complete future " + response); 241 } 242 } 243 isDeviceSupported(UsbDevice device)244 CompletableFuture<Boolean> isDeviceSupported(UsbDevice device) { 245 return sendMessageForResult( 246 MSG_NEW_DEVICE_ATTACHED, 247 MSG_NEW_DEVICE_ATTACHED_RESPONSE, 248 createUsbDeviceData(device)) 249 .thenApply(this::isResultOk); 250 251 } 252 canSwitchDeviceToAoap(UsbDevice device)253 CompletableFuture<Boolean> canSwitchDeviceToAoap(UsbDevice device) { 254 return sendMessageForResult( 255 MSG_CAN_SWITCH_TO_AOAP, 256 MSG_CAN_SWITCH_TO_AOAP_RESPONSE, 257 createUsbDeviceData(device)) 258 .thenApply(this::isResultOk); 259 } 260 isResultOk(Bundle data)261 private boolean isResultOk(Bundle data) { 262 int result = data.getInt(KEY_RESULT); 263 Log.i(TAG, "Got result: " + data); 264 return AoapService.RESULT_OK == result; 265 } 266 createUsbDeviceData(UsbDevice device)267 private static Bundle createUsbDeviceData(UsbDevice device) { 268 Bundle data = new Bundle(1); 269 data.putParcelable(KEY_DEVICE, device); 270 return data; 271 } 272 sendMessageForResult( int msgRequest, int msgResponse, Bundle data)273 private CompletableFuture<Bundle> sendMessageForResult( 274 int msgRequest, int msgResponse, Bundle data) { 275 return mConnected.thenCompose(x -> { 276 CompletableFuture<Bundle> responseFuture = new CompletableFuture<>(); 277 Messenger messenger; 278 synchronized (mLock) { 279 mExpectedResponses.put(msgResponse, responseFuture); 280 messenger = mOutgoingMessenger; 281 } 282 send(messenger, msgRequest, data); 283 284 return responseFuture; 285 }); 286 } 287 send(Messenger messenger, int req, Bundle data)288 private void send(Messenger messenger, int req, Bundle data) { 289 Message msg = Message.obtain(null, req, null); 290 msg.replyTo = mIncomingMessenger; 291 msg.setData(data); 292 try { 293 messenger.send(msg); 294 } catch (RemoteException e) { 295 throw new RuntimeException("Connection broken with " + mComponentName, e); 296 } 297 } 298 299 @Override toString()300 public String toString() { 301 return "AoapServiceConnection{" 302 + "mBound=" + mBound 303 + ", mConnected=" + mConnected 304 + ", mExpectedResponses=" + mExpectedResponses 305 + ", mComponentName=" + mComponentName 306 + '}'; 307 } 308 } 309 } 310