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