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