1 /* 2 * Copyright (C) 2016 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 package android.car.usb.handler; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.hardware.usb.UsbDevice; 23 import android.hardware.usb.UsbDeviceConnection; 24 import android.hardware.usb.UsbManager; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.Parcel; 29 import android.util.Log; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * Controller used to handle USB device connections. 38 * TODO: Support handling multiple new USB devices at the same time. 39 */ 40 public final class UsbHostController 41 implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback { 42 43 /** 44 * Callbacks for controller 45 */ 46 public interface UsbHostControllerCallbacks { 47 /** Host controller ready for shutdown */ shutdown()48 void shutdown(); 49 50 /** Change of processing state */ processingStarted()51 void processingStarted(); 52 53 /** Title of processing changed */ titleChanged(String title)54 void titleChanged(String title); 55 56 /** Options for USB device changed */ optionsUpdated(List<UsbDeviceSettings> options)57 void optionsUpdated(List<UsbDeviceSettings> options); 58 } 59 60 private static final String TAG = UsbHostController.class.getSimpleName(); 61 private static final boolean LOCAL_LOGD = true; 62 private static final boolean LOCAL_LOGV = true; 63 64 private static final int DISPATCH_RETRY_DELAY_MS = 1000; 65 private static final int DISPATCH_RETRY_ATTEMPTS = 5; 66 67 private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>(); 68 private final Context mContext; 69 private final UsbHostControllerCallbacks mCallback; 70 private final UsbSettingsStorage mUsbSettingsStorage; 71 private final UsbManager mUsbManager; 72 private final UsbDeviceHandlerResolver mUsbResolver; 73 private final UsbHostControllerHandler mHandler; 74 75 private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() { 76 @Override 77 public void onReceive(Context context, Intent intent) { 78 if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { 79 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); 80 unsetActiveDeviceIfMatch(device); 81 } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { 82 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); 83 setActiveDeviceIfMatch(device); 84 } 85 } 86 }; 87 88 private final Object mLock = new Object(); 89 90 @GuardedBy("mLock") 91 private UsbDevice mActiveDevice; 92 UsbHostController(Context context, UsbHostControllerCallbacks callbacks)93 public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) { 94 mContext = context; 95 mCallback = callbacks; 96 mHandler = new UsbHostControllerHandler(Looper.myLooper()); 97 mUsbSettingsStorage = new UsbSettingsStorage(context); 98 mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); 99 mUsbResolver = new UsbDeviceHandlerResolver(mUsbManager, mContext, this); 100 IntentFilter filter = new IntentFilter(); 101 filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); 102 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); 103 context.registerReceiver(mUsbBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); 104 } 105 setActiveDeviceIfMatch(UsbDevice device)106 private void setActiveDeviceIfMatch(UsbDevice device) { 107 synchronized (mLock) { 108 if (mActiveDevice != null && device != null 109 && UsbUtil.isDevicesMatching(device, mActiveDevice)) { 110 mActiveDevice = device; 111 } 112 } 113 } 114 unsetActiveDeviceIfMatch(UsbDevice device)115 private void unsetActiveDeviceIfMatch(UsbDevice device) { 116 mHandler.requestDeviceRemoved(); 117 synchronized (mLock) { 118 if (mActiveDevice != null && device != null 119 && UsbUtil.isDevicesMatching(device, mActiveDevice)) { 120 mActiveDevice = null; 121 } 122 } 123 } 124 startDeviceProcessingIfNull(UsbDevice device)125 private boolean startDeviceProcessingIfNull(UsbDevice device) { 126 synchronized (mLock) { 127 if (mActiveDevice == null) { 128 mActiveDevice = device; 129 return true; 130 } 131 return false; 132 } 133 } 134 stopDeviceProcessing()135 private void stopDeviceProcessing() { 136 synchronized (mLock) { 137 mActiveDevice = null; 138 } 139 } 140 getActiveDevice()141 private UsbDevice getActiveDevice() { 142 synchronized (mLock) { 143 Parcel parcel = Parcel.obtain(); 144 try { 145 parcel.writeParcelable(mActiveDevice, 0); 146 parcel.setDataPosition(0); 147 return parcel.readParcelable(UsbDevice.class.getClassLoader(), UsbDevice.class); 148 } finally { 149 parcel.recycle(); 150 } 151 } 152 } 153 deviceMatchedActiveDevice(UsbDevice device)154 private boolean deviceMatchedActiveDevice(UsbDevice device) { 155 UsbDevice activeDevice = getActiveDevice(); 156 return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device); 157 } 158 generateTitle(Context context, UsbDevice usbDevice)159 private static String generateTitle(Context context, UsbDevice usbDevice) { 160 String manufacturer = usbDevice.getManufacturerName(); 161 String product = usbDevice.getProductName(); 162 if (manufacturer == null && product == null) { 163 return context.getString(R.string.usb_unknown_device); 164 } 165 if (manufacturer != null && product != null) { 166 return manufacturer + " " + product; 167 } 168 if (manufacturer != null) { 169 return manufacturer; 170 } 171 return product; 172 } 173 174 /** 175 * Processes device new device. 176 * <p> 177 * It will load existing settings or resolve supported handlers. 178 */ processDevice(UsbDevice device)179 public void processDevice(UsbDevice device) { 180 if (!startDeviceProcessingIfNull(device)) { 181 Log.w(TAG, "Currently, other device is being processed"); 182 } 183 mCallback.optionsUpdated(mEmptyList); 184 mCallback.processingStarted(); 185 186 UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device); 187 188 if (settings == null) { 189 resolveDevice(device); 190 } else { 191 Object obj = 192 new UsbHostControllerHandlerDispatchData( 193 device, settings, DISPATCH_RETRY_ATTEMPTS, true); 194 Message.obtain(mHandler, UsbHostControllerHandler.MSG_DEVICE_DISPATCH, obj) 195 .sendToTarget(); 196 } 197 } 198 199 /** 200 * Applies device settings. 201 */ applyDeviceSettings(UsbDeviceSettings settings)202 public void applyDeviceSettings(UsbDeviceSettings settings) { 203 mUsbSettingsStorage.saveSettings(settings); 204 Message msg = mHandler.obtainMessage(); 205 msg.obj = 206 new UsbHostControllerHandlerDispatchData( 207 getActiveDevice(), settings, DISPATCH_RETRY_ATTEMPTS, false); 208 msg.what = UsbHostControllerHandler.MSG_DEVICE_DISPATCH; 209 msg.sendToTarget(); 210 } 211 resolveDevice(UsbDevice device)212 private void resolveDevice(UsbDevice device) { 213 mCallback.titleChanged(generateTitle(mContext, device)); 214 mUsbResolver.resolve(device); 215 } 216 217 /** 218 * Release object. 219 */ release()220 public void release() { 221 mContext.unregisterReceiver(mUsbBroadcastReceiver); 222 mUsbResolver.release(); 223 } 224 isDeviceAoapPossible(UsbDevice device)225 private boolean isDeviceAoapPossible(UsbDevice device) { 226 if (AoapInterface.isDeviceInAoapMode(device)) { 227 return true; 228 } 229 230 UsbManager usbManager = mContext.getSystemService(UsbManager.class); 231 UsbDeviceConnection connection = UsbUtil.openConnection(usbManager, device); 232 // USB Manager can return null connection if the device is failed to open one. 233 if (connection == null) { 234 return false; 235 } 236 boolean aoapSupported = AoapInterface.isSupported(mContext, device, connection); 237 connection.close(); 238 239 return aoapSupported; 240 } 241 242 @Override onHandlersResolveCompleted( UsbDevice device, List<UsbDeviceSettings> handlers)243 public void onHandlersResolveCompleted( 244 UsbDevice device, List<UsbDeviceSettings> handlers) { 245 if (LOCAL_LOGD) { 246 Log.d(TAG, "onHandlersResolveComplete: " + device); 247 } 248 if (deviceMatchedActiveDevice(device)) { 249 if (handlers.isEmpty()) { 250 onDeviceDispatched(); 251 } else if (handlers.size() == 1) { 252 applyDeviceSettings(handlers.get(0)); 253 } else { 254 if (isDeviceAoapPossible(device)) { 255 // Device supports AOAP mode, if we have just single AOAP handler then use it 256 // instead of showing disambiguation dialog to the user. 257 UsbDeviceSettings aoapHandler = getSingleAoapDeviceHandlerOrNull(handlers); 258 if (aoapHandler != null) { 259 applyDeviceSettings(aoapHandler); 260 return; 261 } 262 } 263 mCallback.optionsUpdated(handlers); 264 } 265 } else { 266 Log.w(TAG, "Handlers ignored as they came for inactive device"); 267 } 268 } 269 getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers)270 private UsbDeviceSettings getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers) { 271 UsbDeviceSettings aoapHandler = null; 272 for (UsbDeviceSettings handler : handlers) { 273 if (handler.isAaop()) { 274 if (aoapHandler != null) { // Found multiple AOAP handlers. 275 return null; 276 } 277 aoapHandler = handler; 278 } 279 } 280 return aoapHandler; 281 } 282 283 @Override onDeviceDispatched()284 public void onDeviceDispatched() { 285 stopDeviceProcessing(); 286 mCallback.shutdown(); 287 } 288 doHandleDeviceRemoved()289 void doHandleDeviceRemoved() { 290 if (getActiveDevice() == null) { 291 if (LOCAL_LOGD) { 292 Log.d(TAG, "USB device detached"); 293 } 294 stopDeviceProcessing(); 295 mCallback.shutdown(); 296 } 297 } 298 299 private class UsbHostControllerHandlerDispatchData { 300 private final UsbDevice mUsbDevice; 301 private final UsbDeviceSettings mUsbDeviceSettings; 302 303 public int mRetries = 0; 304 public boolean mCanResolve = true; 305 UsbHostControllerHandlerDispatchData( UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, int retries, boolean canResolve)306 public UsbHostControllerHandlerDispatchData( 307 UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, 308 int retries, boolean canResolve) { 309 mUsbDevice = usbDevice; 310 mUsbDeviceSettings = usbDeviceSettings; 311 mRetries = retries; 312 mCanResolve = canResolve; 313 } 314 getUsbDevice()315 public UsbDevice getUsbDevice() { 316 return mUsbDevice; 317 } 318 getUsbDeviceSettings()319 public UsbDeviceSettings getUsbDeviceSettings() { 320 return mUsbDeviceSettings; 321 } 322 } 323 324 private class UsbHostControllerHandler extends Handler { 325 private static final int MSG_DEVICE_REMOVED = 1; 326 private static final int MSG_DEVICE_DISPATCH = 2; 327 328 private static final int DEVICE_REMOVE_TIMEOUT_MS = 500; 329 330 // Used to get the device that we are trying to connect to, if mActiveDevice is removed and 331 // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when 332 // there are multiple devices attached. 333 private int mLastDeviceId = 0; 334 private int mStartAoapRetries = 1; 335 UsbHostControllerHandler(Looper looper)336 private UsbHostControllerHandler(Looper looper) { 337 super(looper); 338 } 339 requestDeviceRemoved()340 private void requestDeviceRemoved() { 341 sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS); 342 } 343 onFailure(UsbDevice failedDevice)344 private void onFailure(UsbDevice failedDevice) { 345 if (mStartAoapRetries == 0) { 346 Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake."); 347 return; 348 } 349 mStartAoapRetries--; 350 351 UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, failedDevice); 352 if (connection != null) { 353 Log.d(TAG, "Resetting USB device."); 354 connection.resetDevice(); 355 } 356 357 Log.d(TAG, "Restarting USB enumeration."); 358 for (UsbDevice device : mUsbManager.getDeviceList().values()) { 359 if (mLastDeviceId == device.getDeviceId()) { 360 processDevice(device); 361 return; 362 } 363 } 364 } 365 366 @Override handleMessage(Message msg)367 public void handleMessage(Message msg) { 368 switch (msg.what) { 369 case MSG_DEVICE_REMOVED: 370 doHandleDeviceRemoved(); 371 break; 372 case MSG_DEVICE_DISPATCH: 373 UsbHostControllerHandlerDispatchData data = 374 (UsbHostControllerHandlerDispatchData) msg.obj; 375 UsbDevice device = data.getUsbDevice(); 376 mLastDeviceId = device.getDeviceId(); 377 UsbDeviceSettings settings = data.getUsbDeviceSettings(); 378 if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.isAaop(), 379 this::onFailure)) { 380 if (data.mRetries > 0) { 381 --data.mRetries; 382 Message nextMessage = Message.obtain(msg); 383 mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS); 384 } else if (data.mCanResolve) { 385 resolveDevice(device); 386 } 387 } else if (LOCAL_LOGV) { 388 Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: " 389 + settings.getHandler()); 390 } 391 break; 392 default: 393 Log.w(TAG, "Unhandled message: " + msg); 394 super.handleMessage(msg); 395 } 396 } 397 } 398 399 } 400