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 boolean aoapSupported = AoapInterface.isSupported(mContext, device, connection); 233 connection.close(); 234 235 return aoapSupported; 236 } 237 238 @Override onHandlersResolveCompleted( UsbDevice device, List<UsbDeviceSettings> handlers)239 public void onHandlersResolveCompleted( 240 UsbDevice device, List<UsbDeviceSettings> handlers) { 241 if (LOCAL_LOGD) { 242 Log.d(TAG, "onHandlersResolveComplete: " + device); 243 } 244 if (deviceMatchedActiveDevice(device)) { 245 if (handlers.isEmpty()) { 246 onDeviceDispatched(); 247 } else if (handlers.size() == 1) { 248 applyDeviceSettings(handlers.get(0)); 249 } else { 250 if (isDeviceAoapPossible(device)) { 251 // Device supports AOAP mode, if we have just single AOAP handler then use it 252 // instead of showing disambiguation dialog to the user. 253 UsbDeviceSettings aoapHandler = getSingleAoapDeviceHandlerOrNull(handlers); 254 if (aoapHandler != null) { 255 applyDeviceSettings(aoapHandler); 256 return; 257 } 258 } 259 mCallback.optionsUpdated(handlers); 260 } 261 } else { 262 Log.w(TAG, "Handlers ignored as they came for inactive device"); 263 } 264 } 265 getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers)266 private UsbDeviceSettings getSingleAoapDeviceHandlerOrNull(List<UsbDeviceSettings> handlers) { 267 UsbDeviceSettings aoapHandler = null; 268 for (UsbDeviceSettings handler : handlers) { 269 if (handler.isAaop()) { 270 if (aoapHandler != null) { // Found multiple AOAP handlers. 271 return null; 272 } 273 aoapHandler = handler; 274 } 275 } 276 return aoapHandler; 277 } 278 279 @Override onDeviceDispatched()280 public void onDeviceDispatched() { 281 stopDeviceProcessing(); 282 mCallback.shutdown(); 283 } 284 doHandleDeviceRemoved()285 void doHandleDeviceRemoved() { 286 if (getActiveDevice() == null) { 287 if (LOCAL_LOGD) { 288 Log.d(TAG, "USB device detached"); 289 } 290 stopDeviceProcessing(); 291 mCallback.shutdown(); 292 } 293 } 294 295 private class UsbHostControllerHandlerDispatchData { 296 private final UsbDevice mUsbDevice; 297 private final UsbDeviceSettings mUsbDeviceSettings; 298 299 public int mRetries = 0; 300 public boolean mCanResolve = true; 301 UsbHostControllerHandlerDispatchData( UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, int retries, boolean canResolve)302 public UsbHostControllerHandlerDispatchData( 303 UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings, 304 int retries, boolean canResolve) { 305 mUsbDevice = usbDevice; 306 mUsbDeviceSettings = usbDeviceSettings; 307 mRetries = retries; 308 mCanResolve = canResolve; 309 } 310 getUsbDevice()311 public UsbDevice getUsbDevice() { 312 return mUsbDevice; 313 } 314 getUsbDeviceSettings()315 public UsbDeviceSettings getUsbDeviceSettings() { 316 return mUsbDeviceSettings; 317 } 318 } 319 320 private class UsbHostControllerHandler extends Handler { 321 private static final int MSG_DEVICE_REMOVED = 1; 322 private static final int MSG_DEVICE_DISPATCH = 2; 323 324 private static final int DEVICE_REMOVE_TIMEOUT_MS = 500; 325 326 // Used to get the device that we are trying to connect to, if mActiveDevice is removed and 327 // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when 328 // there are multiple devices attached. 329 private int mLastDeviceId = 0; 330 private int mStartAoapRetries = 1; 331 UsbHostControllerHandler(Looper looper)332 private UsbHostControllerHandler(Looper looper) { 333 super(looper); 334 } 335 requestDeviceRemoved()336 private void requestDeviceRemoved() { 337 sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS); 338 } 339 onFailure(UsbDevice failedDevice)340 private void onFailure(UsbDevice failedDevice) { 341 if (mStartAoapRetries == 0) { 342 Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake."); 343 return; 344 } 345 mStartAoapRetries--; 346 347 UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, failedDevice); 348 if (connection != null) { 349 Log.d(TAG, "Resetting USB device."); 350 connection.resetDevice(); 351 } 352 353 Log.d(TAG, "Restarting USB enumeration."); 354 for (UsbDevice device : mUsbManager.getDeviceList().values()) { 355 if (mLastDeviceId == device.getDeviceId()) { 356 processDevice(device); 357 return; 358 } 359 } 360 } 361 362 @Override handleMessage(Message msg)363 public void handleMessage(Message msg) { 364 switch (msg.what) { 365 case MSG_DEVICE_REMOVED: 366 doHandleDeviceRemoved(); 367 break; 368 case MSG_DEVICE_DISPATCH: 369 UsbHostControllerHandlerDispatchData data = 370 (UsbHostControllerHandlerDispatchData) msg.obj; 371 UsbDevice device = data.getUsbDevice(); 372 mLastDeviceId = device.getDeviceId(); 373 UsbDeviceSettings settings = data.getUsbDeviceSettings(); 374 if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.isAaop(), 375 this::onFailure)) { 376 if (data.mRetries > 0) { 377 --data.mRetries; 378 Message nextMessage = Message.obtain(msg); 379 mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS); 380 } else if (data.mCanResolve) { 381 resolveDevice(device); 382 } 383 } else if (LOCAL_LOGV) { 384 Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: " 385 + settings.getHandler()); 386 } 387 break; 388 default: 389 Log.w(TAG, "Unhandled message: " + msg); 390 super.handleMessage(msg); 391 } 392 } 393 } 394 395 } 396