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