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