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