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 */ 16package android.car.usb.handler; 17 18import android.car.IUsbAoapSupportCheckService; 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.pm.ActivityInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.content.pm.ResolveInfo; 27import android.content.res.XmlResourceParser; 28import android.hardware.usb.UsbDevice; 29import android.hardware.usb.UsbDeviceConnection; 30import android.hardware.usb.UsbInterface; 31import android.hardware.usb.UsbManager; 32import android.os.Handler; 33import android.os.HandlerThread; 34import android.os.IBinder; 35import android.os.Looper; 36import android.os.Message; 37import android.os.RemoteException; 38import android.util.Log; 39import android.util.Pair; 40import com.android.internal.util.XmlUtils; 41import java.io.IOException; 42import java.util.ArrayList; 43import java.util.LinkedList; 44import java.util.List; 45import java.util.Queue; 46import org.xmlpull.v1.XmlPullParser; 47 48/** Resolves supported handlers for USB device. */ 49public final class UsbDeviceHandlerResolver { 50 private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName(); 51 private static final boolean LOCAL_LOGD = true; 52 53 /** 54 * Callbacks for device resolver. 55 */ 56 public interface UsbDeviceHandlerResolverCallback { 57 /** Handlers are resolved */ 58 void onHandlersResolveCompleted( 59 UsbDevice device, List<UsbDeviceSettings> availableSettings); 60 /** Device was dispatched */ 61 void onDeviceDispatched(); 62 } 63 64 private final UsbManager mUsbManager; 65 private final PackageManager mPackageManager; 66 private final UsbDeviceHandlerResolverCallback mDeviceCallback; 67 private final Context mContext; 68 private final HandlerThread mHandlerThread; 69 private final UsbDeviceResolverHandler mHandler; 70 71 private class DeviceContext { 72 public final UsbDevice usbDevice; 73 public final UsbDeviceConnection connection; 74 public final UsbDeviceSettings settings; 75 public final List<UsbDeviceSettings> activeDeviceSettings; 76 public final Queue<Pair<ResolveInfo, DeviceFilter>> mActiveDeviceOptions = 77 new LinkedList<>(); 78 79 private volatile IUsbAoapSupportCheckService mUsbAoapSupportCheckService; 80 private final ServiceConnection mServiceConnection = new ServiceConnection() { 81 @Override 82 public void onServiceConnected(ComponentName className, IBinder service) { 83 Log.i(TAG, "onServiceConnected: " + className); 84 mUsbAoapSupportCheckService = IUsbAoapSupportCheckService.Stub.asInterface(service); 85 mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this); 86 } 87 88 @Override 89 public void onServiceDisconnected(ComponentName className) { 90 Log.i(TAG, "onServiceDisconnected: " + className); 91 mUsbAoapSupportCheckService = null; 92 mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this); 93 } 94 }; 95 96 public DeviceContext(UsbDevice usbDevice, UsbDeviceSettings settings, 97 List<UsbDeviceSettings> activeDeviceSettings) { 98 this.usbDevice = usbDevice; 99 this.settings = settings; 100 this.activeDeviceSettings = activeDeviceSettings; 101 connection = UsbUtil.openConnection(mUsbManager, usbDevice); 102 } 103 } 104 105 // This class is used to describe a USB device. 106 // When used in HashMaps all values must be specified, 107 // but wildcards can be used for any of the fields in 108 // the package meta-data. 109 private static class DeviceFilter { 110 // USB Vendor ID (or -1 for unspecified) 111 public final int mVendorId; 112 // USB Product ID (or -1 for unspecified) 113 public final int mProductId; 114 // USB device or interface class (or -1 for unspecified) 115 public final int mClass; 116 // USB device subclass (or -1 for unspecified) 117 public final int mSubclass; 118 // USB device protocol (or -1 for unspecified) 119 public final int mProtocol; 120 // USB device manufacturer name string (or null for unspecified) 121 public final String mManufacturerName; 122 // USB device product name string (or null for unspecified) 123 public final String mProductName; 124 // USB device serial number string (or null for unspecified) 125 public final String mSerialNumber; 126 127 // USB device in AOAP mode manufacturer 128 public final String mAoapManufacturer; 129 // USB device in AOAP mode model 130 public final String mAoapModel; 131 // USB device in AOAP mode description string 132 public final String mAoapDescription; 133 // USB device in AOAP mode version 134 public final String mAoapVersion; 135 // USB device in AOAP mode URI 136 public final String mAoapUri; 137 // USB device in AOAP mode serial 138 public final String mAoapSerial; 139 // USB device in AOAP mode verification service 140 public final String mAoapService; 141 142 DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol, 143 String manufacturer, String product, String serialnum, 144 String aoapManufacturer, String aoapModel, String aoapDescription, 145 String aoapVersion, String aoapUri, String aoapSerial, 146 String aoapService) { 147 mVendorId = vid; 148 mProductId = pid; 149 mClass = clasz; 150 mSubclass = subclass; 151 mProtocol = protocol; 152 mManufacturerName = manufacturer; 153 mProductName = product; 154 mSerialNumber = serialnum; 155 156 mAoapManufacturer = aoapManufacturer; 157 mAoapModel = aoapModel; 158 mAoapDescription = aoapDescription; 159 mAoapVersion = aoapVersion; 160 mAoapUri = aoapUri; 161 mAoapSerial = aoapSerial; 162 mAoapService = aoapService; 163 } 164 165 DeviceFilter(UsbDevice device) { 166 mVendorId = device.getVendorId(); 167 mProductId = device.getProductId(); 168 mClass = device.getDeviceClass(); 169 mSubclass = device.getDeviceSubclass(); 170 mProtocol = device.getDeviceProtocol(); 171 mManufacturerName = device.getManufacturerName(); 172 mProductName = device.getProductName(); 173 mSerialNumber = device.getSerialNumber(); 174 mAoapManufacturer = null; 175 mAoapModel = null; 176 mAoapDescription = null; 177 mAoapVersion = null; 178 mAoapUri = null; 179 mAoapSerial = null; 180 mAoapService = null; 181 } 182 183 public static DeviceFilter read(XmlPullParser parser, boolean aoapData) { 184 int vendorId = -1; 185 int productId = -1; 186 int deviceClass = -1; 187 int deviceSubclass = -1; 188 int deviceProtocol = -1; 189 String manufacturerName = null; 190 String productName = null; 191 String serialNumber = null; 192 193 String aoapManufacturer = null; 194 String aoapModel = null; 195 String aoapDescription = null; 196 String aoapVersion = null; 197 String aoapUri = null; 198 String aoapSerial = null; 199 String aoapService = null; 200 201 int count = parser.getAttributeCount(); 202 for (int i = 0; i < count; i++) { 203 String name = parser.getAttributeName(i); 204 String value = parser.getAttributeValue(i); 205 // Attribute values are ints or strings 206 if (!aoapData && "manufacturer-name".equals(name)) { 207 manufacturerName = value; 208 } else if (!aoapData && "product-name".equals(name)) { 209 productName = value; 210 } else if (!aoapData && "serial-number".equals(name)) { 211 serialNumber = value; 212 } else if (aoapData && "manufacturer".equals(name)) { 213 aoapManufacturer = value; 214 } else if (aoapData && "model".equals(name)) { 215 aoapModel = value; 216 } else if (aoapData && "description".equals(name)) { 217 aoapDescription = value; 218 } else if (aoapData && "version".equals(name)) { 219 aoapVersion = value; 220 } else if (aoapData && "uri".equals(name)) { 221 aoapUri = value; 222 } else if (aoapData && "serial".equals(name)) { 223 aoapSerial = value; 224 } else if (aoapData && "service".equals(name)) { 225 aoapService = value; 226 } else if (!aoapData) { 227 int intValue = -1; 228 int radix = 10; 229 if (value != null && value.length() > 2 && value.charAt(0) == '0' 230 && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) { 231 // allow hex values starting with 0x or 0X 232 radix = 16; 233 value = value.substring(2); 234 } 235 try { 236 intValue = Integer.parseInt(value, radix); 237 } catch (NumberFormatException e) { 238 Log.e(TAG, "invalid number for field " + name, e); 239 continue; 240 } 241 if ("vendor-id".equals(name)) { 242 vendorId = intValue; 243 } else if ("product-id".equals(name)) { 244 productId = intValue; 245 } else if ("class".equals(name)) { 246 deviceClass = intValue; 247 } else if ("subclass".equals(name)) { 248 deviceSubclass = intValue; 249 } else if ("protocol".equals(name)) { 250 deviceProtocol = intValue; 251 } 252 } 253 } 254 return new DeviceFilter(vendorId, productId, 255 deviceClass, deviceSubclass, deviceProtocol, 256 manufacturerName, productName, serialNumber, aoapManufacturer, 257 aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial, 258 aoapService); 259 } 260 261 private boolean matches(int clasz, int subclass, int protocol) { 262 return ((mClass == -1 || clasz == mClass) 263 && (mSubclass == -1 || subclass == mSubclass) 264 && (mProtocol == -1 || protocol == mProtocol)); 265 } 266 267 public boolean matches(UsbDevice device) { 268 if (mVendorId != -1 && device.getVendorId() != mVendorId) { 269 return false; 270 } 271 if (mProductId != -1 && device.getProductId() != mProductId) { 272 return false; 273 } 274 if (mManufacturerName != null && device.getManufacturerName() == null) { 275 return false; 276 } 277 if (mProductName != null && device.getProductName() == null) { 278 return false; 279 } 280 if (mSerialNumber != null && device.getSerialNumber() == null) { 281 return false; 282 } 283 if (mManufacturerName != null && device.getManufacturerName() != null 284 && !mManufacturerName.equals(device.getManufacturerName())) { 285 return false; 286 } 287 if (mProductName != null && device.getProductName() != null 288 && !mProductName.equals(device.getProductName())) { 289 return false; 290 } 291 if (mSerialNumber != null && device.getSerialNumber() != null 292 && !mSerialNumber.equals(device.getSerialNumber())) { 293 return false; 294 } 295 296 // check device class/subclass/protocol 297 if (matches(device.getDeviceClass(), device.getDeviceSubclass(), 298 device.getDeviceProtocol())) { 299 return true; 300 } 301 302 // if device doesn't match, check the interfaces 303 int count = device.getInterfaceCount(); 304 for (int i = 0; i < count; i++) { 305 UsbInterface intf = device.getInterface(i); 306 if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), 307 intf.getInterfaceProtocol())) { 308 return true; 309 } 310 } 311 312 return false; 313 } 314 315 @Override 316 public boolean equals(Object obj) { 317 // can't compare if we have wildcard strings 318 if (mVendorId == -1 || mProductId == -1 319 || mClass == -1 || mSubclass == -1 || mProtocol == -1) { 320 return false; 321 } 322 if (obj instanceof DeviceFilter) { 323 DeviceFilter filter = (DeviceFilter) obj; 324 325 if (filter.mVendorId != mVendorId 326 || filter.mProductId != mProductId 327 || filter.mClass != mClass 328 || filter.mSubclass != mSubclass 329 || filter.mProtocol != mProtocol) { 330 return false; 331 } 332 if ((filter.mManufacturerName != null && mManufacturerName == null) 333 || (filter.mManufacturerName == null && mManufacturerName != null) 334 || (filter.mProductName != null && mProductName == null) 335 || (filter.mProductName == null && mProductName != null) 336 || (filter.mSerialNumber != null && mSerialNumber == null) 337 || (filter.mSerialNumber == null && mSerialNumber != null)) { 338 return false; 339 } 340 if ((filter.mManufacturerName != null && mManufacturerName != null 341 && !mManufacturerName.equals(filter.mManufacturerName)) 342 || (filter.mProductName != null && mProductName != null 343 && !mProductName.equals(filter.mProductName)) 344 || (filter.mSerialNumber != null && mSerialNumber != null 345 && !mSerialNumber.equals(filter.mSerialNumber))) { 346 return false; 347 } 348 return true; 349 } 350 if (obj instanceof UsbDevice) { 351 UsbDevice device = (UsbDevice) obj; 352 if (device.getVendorId() != mVendorId 353 || device.getProductId() != mProductId 354 || device.getDeviceClass() != mClass 355 || device.getDeviceSubclass() != mSubclass 356 || device.getDeviceProtocol() != mProtocol) { 357 return false; 358 } 359 if ((mManufacturerName != null && device.getManufacturerName() == null) 360 || (mManufacturerName == null && device.getManufacturerName() != null) 361 || (mProductName != null && device.getProductName() == null) 362 || (mProductName == null && device.getProductName() != null) 363 || (mSerialNumber != null && device.getSerialNumber() == null) 364 || (mSerialNumber == null && device.getSerialNumber() != null)) { 365 return false; 366 } 367 if ((device.getManufacturerName() != null 368 && !mManufacturerName.equals(device.getManufacturerName())) 369 || (device.getProductName() != null 370 && !mProductName.equals(device.getProductName())) 371 || (device.getSerialNumber() != null 372 && !mSerialNumber.equals(device.getSerialNumber()))) { 373 return false; 374 } 375 return true; 376 } 377 return false; 378 } 379 380 @Override 381 public int hashCode() { 382 return (((mVendorId << 16) | mProductId) 383 ^ ((mClass << 16) | (mSubclass << 8) | mProtocol)); 384 } 385 386 @Override 387 public String toString() { 388 return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId 389 + ",mClass=" + mClass + ",mSubclass=" + mSubclass 390 + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName 391 + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]"; 392 } 393 } 394 395 public UsbDeviceHandlerResolver(UsbManager manager, Context context, 396 UsbDeviceHandlerResolverCallback deviceListener) { 397 mUsbManager = manager; 398 mContext = context; 399 mDeviceCallback = deviceListener; 400 mHandlerThread = new HandlerThread(TAG); 401 mHandlerThread.start(); 402 mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper()); 403 mPackageManager = context.getPackageManager(); 404 } 405 406 /** 407 * Releases current object. 408 */ 409 public void release() { 410 if (mHandlerThread != null) { 411 mHandlerThread.quitSafely(); 412 } 413 } 414 415 /** 416 * Resolves handlers for USB device. 417 */ 418 public void resolve(UsbDevice device) { 419 mHandler.requestResolveHandlers(device); 420 } 421 422 /** 423 * Dispatches device to component. 424 */ 425 public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) { 426 if (LOCAL_LOGD) { 427 Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap); 428 } 429 430 ActivityInfo activityInfo; 431 try { 432 activityInfo = mPackageManager.getActivityInfo(component, PackageManager.GET_META_DATA); 433 } catch (NameNotFoundException e) { 434 Log.e(TAG, "Activity not found: " + component); 435 return false; 436 } 437 438 Intent intent = createDeviceAttachedIntent(device); 439 if (inAoap) { 440 if (AoapInterface.isDeviceInAoapMode(device)) { 441 mDeviceCallback.onDeviceDispatched(); 442 } else { 443 DeviceFilter filter = 444 packageMatches(activityInfo, intent.getAction(), device, true); 445 if (filter != null) { 446 requestAoapSwitch(device, filter); 447 return true; 448 } 449 } 450 } 451 452 intent.setComponent(component); 453 mUsbManager.grantPermission(device, activityInfo.applicationInfo.uid); 454 455 mContext.startActivity(intent); 456 mHandler.requestCompleteDeviceDispatch(); 457 return true; 458 } 459 460 private static Intent createDeviceAttachedIntent(UsbDevice device) { 461 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); 462 intent.putExtra(UsbManager.EXTRA_DEVICE, device); 463 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 464 return intent; 465 } 466 467 private void doHandleResolveHandlers(UsbDevice device) { 468 if (LOCAL_LOGD) { 469 Log.d(TAG, "doHandleResolveHandlers: " + device); 470 } 471 472 Intent intent = createDeviceAttachedIntent(device); 473 List<Pair<ResolveInfo, DeviceFilter>> matches = getDeviceMatches(device, intent, false); 474 if (LOCAL_LOGD) { 475 Log.d(TAG, "matches size: " + matches.size()); 476 } 477 List<UsbDeviceSettings> settings = new ArrayList<>(matches.size()); 478 for (Pair<ResolveInfo, DeviceFilter> info : matches) { 479 UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(device); 480 setting.setHandler( 481 new ComponentName( 482 info.first.activityInfo.packageName, info.first.activityInfo.name)); 483 settings.add(setting); 484 } 485 DeviceContext deviceContext = 486 new DeviceContext(device, UsbDeviceSettings.constructSettings(device), settings); 487 if (AoapInterface.isSupported(deviceContext.connection)) { 488 deviceContext.mActiveDeviceOptions.addAll(getDeviceMatches(device, intent, true)); 489 queryNextAoapHandler(deviceContext); 490 } else { 491 deviceProbingComplete(deviceContext); 492 } 493 } 494 495 private void queryNextAoapHandler(DeviceContext context) { 496 Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek(); 497 if (option == null) { 498 Log.w(TAG, "No more options left."); 499 deviceProbingComplete(context); 500 return; 501 } 502 Intent serviceIntent = new Intent(); 503 serviceIntent.setComponent(ComponentName.unflattenFromString(option.second.mAoapService)); 504 boolean bound = mContext.bindService(serviceIntent, context.mServiceConnection, 505 Context.BIND_AUTO_CREATE); 506 if (bound) { 507 mHandler.requestServiceConnectionTimeout(); 508 } else { 509 if (LOCAL_LOGD) { 510 Log.d(TAG, "Failed to bind to the service"); 511 } 512 context.mActiveDeviceOptions.poll(); 513 queryNextAoapHandler(context); 514 } 515 } 516 517 private void requestAoapSwitch(UsbDevice device, DeviceFilter filter) { 518 UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device); 519 try { 520 UsbUtil.sendAoapAccessoryStart( 521 connection, 522 filter.mAoapManufacturer, 523 filter.mAoapModel, 524 filter.mAoapDescription, 525 filter.mAoapVersion, 526 filter.mAoapUri, 527 filter.mAoapSerial); 528 } catch (IOException e) { 529 Log.w(TAG, "Failed to switch device into AOAP mode", e); 530 } 531 connection.close(); 532 } 533 534 private void deviceProbingComplete(DeviceContext context) { 535 if (LOCAL_LOGD) { 536 Log.d(TAG, "deviceProbingComplete"); 537 } 538 mDeviceCallback.onHandlersResolveCompleted(context.usbDevice, context.activeDeviceSettings); 539 } 540 541 private void doHandleServiceConnectionStateChanged(DeviceContext context) { 542 if (LOCAL_LOGD) { 543 Log.d(TAG, "doHandleServiceConnectionStateChanged: " 544 + context.mUsbAoapSupportCheckService); 545 } 546 if (context.mUsbAoapSupportCheckService != null) { 547 boolean deviceSupported = false; 548 try { 549 deviceSupported = 550 context.mUsbAoapSupportCheckService.isDeviceSupported(context.usbDevice); 551 } catch (RemoteException e) { 552 Log.e(TAG, "Call to remote service failed", e); 553 } 554 if (deviceSupported) { 555 Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek(); 556 557 UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(context.settings); 558 setting.setHandler( 559 new ComponentName( 560 option.first.activityInfo.packageName, option.first.activityInfo.name)); 561 setting.setAoap(true); 562 context.activeDeviceSettings.add(setting); 563 } 564 mContext.unbindService(context.mServiceConnection); 565 } 566 context.mActiveDeviceOptions.poll(); 567 queryNextAoapHandler(context); 568 } 569 570 private List<Pair<ResolveInfo, DeviceFilter>> getDeviceMatches( 571 UsbDevice device, Intent intent, boolean forAoap) { 572 List<Pair<ResolveInfo, DeviceFilter>> matches = new ArrayList<>(); 573 List<ResolveInfo> resolveInfos = 574 mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA); 575 for (ResolveInfo resolveInfo : resolveInfos) { 576 DeviceFilter filter = packageMatches(resolveInfo.activityInfo, 577 intent.getAction(), device, forAoap); 578 if (filter != null) { 579 matches.add(Pair.create(resolveInfo, filter)); 580 } 581 } 582 return matches; 583 } 584 585 private DeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device, 586 boolean forAoap) { 587 if (LOCAL_LOGD) { 588 Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: " 589 + forAoap); 590 } 591 String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device"; 592 XmlResourceParser parser = null; 593 try { 594 parser = ai.loadXmlMetaData(mPackageManager, metaDataName); 595 if (parser == null) { 596 Log.w(TAG, "no meta-data for " + ai); 597 return null; 598 } 599 600 XmlUtils.nextElement(parser); 601 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 602 String tagName = parser.getName(); 603 if (device != null && filterTagName.equals(tagName)) { 604 DeviceFilter filter = DeviceFilter.read(parser, forAoap); 605 if (forAoap || filter.matches(device)) { 606 return filter; 607 } 608 } 609 XmlUtils.nextElement(parser); 610 } 611 } catch (Exception e) { 612 Log.w(TAG, "Unable to load component info " + ai.toString(), e); 613 } finally { 614 if (parser != null) parser.close(); 615 } 616 return null; 617 } 618 619 private class UsbDeviceResolverHandler extends Handler { 620 private static final int MSG_RESOLVE_HANDLERS = 0; 621 private static final int MSG_SERVICE_CONNECTION_STATE_CHANGE = 1; 622 private static final int MSG_SERVICE_CONNECTION_TIMEOUT = 2; 623 private static final int MSG_COMPLETE_DISPATCH = 3; 624 625 private static final long CONNECT_TIMEOUT_MS = 5000; 626 627 private UsbDeviceResolverHandler(Looper looper) { 628 super(looper); 629 } 630 631 public void requestResolveHandlers(UsbDevice device) { 632 Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device); 633 sendMessage(msg); 634 } 635 636 public void requestOnServiceConnectionStateChanged(DeviceContext deviceContext) { 637 sendMessage(obtainMessage(MSG_SERVICE_CONNECTION_STATE_CHANGE, deviceContext)); 638 } 639 640 public void requestServiceConnectionTimeout() { 641 sendEmptyMessageDelayed(MSG_SERVICE_CONNECTION_TIMEOUT, CONNECT_TIMEOUT_MS); 642 } 643 644 public void requestCompleteDeviceDispatch() { 645 sendEmptyMessage(MSG_COMPLETE_DISPATCH); 646 } 647 648 @Override 649 public void handleMessage(Message msg) { 650 switch (msg.what) { 651 case MSG_RESOLVE_HANDLERS: 652 doHandleResolveHandlers((UsbDevice) msg.obj); 653 break; 654 case MSG_SERVICE_CONNECTION_STATE_CHANGE: 655 removeMessages(MSG_SERVICE_CONNECTION_TIMEOUT); 656 doHandleServiceConnectionStateChanged((DeviceContext) msg.obj); 657 break; 658 case MSG_SERVICE_CONNECTION_TIMEOUT: 659 Log.i(TAG, "Service connection timeout"); 660 doHandleServiceConnectionStateChanged(null); 661 break; 662 case MSG_COMPLETE_DISPATCH: 663 mDeviceCallback.onDeviceDispatched(); 664 break; 665 default: 666 Log.w(TAG, "Unsupported message: " + msg); 667 } 668 } 669 } 670} 671