1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.helper.aoa;
17 
18 import static com.google.common.base.Preconditions.checkNotNull;
19 
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.common.util.concurrent.Uninterruptibles;
22 import com.sun.jna.Native;
23 import com.sun.jna.Pointer;
24 import com.sun.jna.ptr.PointerByReference;
25 
26 import java.time.Duration;
27 import java.time.Instant;
28 import java.util.Arrays;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.Set;
32 import java.util.concurrent.TimeUnit;
33 
34 import javax.annotation.Nonnull;
35 import javax.annotation.Nullable;
36 
37 /**
38  * USB and AOAv2 device manager, from which devices are retrieved.
39  *
40  * @see <a href="https://source.android.com/devices/accessories/aoa2">Android Open Accessory
41  *     Protocol 2.0</a>
42  */
43 public class UsbHelper implements AutoCloseable {
44 
45     // Interval used when waiting for a device
46     private static final Duration POLL_INTERVAL = Duration.ofSeconds(1L);
47     // Default transfer timeout used by all managed devices
48     private static final Duration DEFAULT_TRANSFER_TIMEOUT = Duration.ofSeconds(10L);
49 
50     private final IUsbNative mUsb;
51     private Pointer mContext;
52 
53     private Duration mTransferTimeout = DEFAULT_TRANSFER_TIMEOUT;
54 
UsbHelper()55     public UsbHelper() {
56         this((IUsbNative) Native.loadLibrary("usb-1.0", IUsbNative.class));
57     }
58 
59     @VisibleForTesting
UsbHelper(@onnull IUsbNative usb)60     UsbHelper(@Nonnull IUsbNative usb) {
61         mUsb = usb;
62         // initialize context
63         PointerByReference context = new PointerByReference();
64         checkResult(mUsb.libusb_init(context));
65         mContext = context.getValue();
66     }
67 
68     /** @return native libusb adapter */
getUsb()69     IUsbNative getUsb() {
70         return mUsb;
71     }
72 
73     /** @return default transfer timeout (Duration.ZERO indicates unlimited timeout). */
getTransferTimeout()74     public Duration getTransferTimeout() {
75         return mTransferTimeout;
76     }
77 
78     /** Sets the default transfer timeout used by all managed devices. */
setTransferTimeout(@onnull Duration transferTimeout)79     public void setTransferTimeout(@Nonnull Duration transferTimeout) {
80         mTransferTimeout = transferTimeout;
81     }
82 
83     /**
84      * Verifies a USB response, throwing an exception if it corresponds to an error.
85      *
86      * @param value result or error code
87      */
checkResult(int value)88     public int checkResult(int value) {
89         if (value < 0) {
90             throw new UsbException(mUsb.libusb_strerror(value));
91         }
92         return value;
93     }
94 
95     /**
96      * Find all connected device serial numbers.
97      *
98      * @param aoaOnly whether to only include AOAv2-compatible devices
99      * @return USB device serial numbers
100      */
101     @Nonnull
getSerialNumbers(boolean aoaOnly)102     public Set<String> getSerialNumbers(boolean aoaOnly) {
103         Set<String> serialNumbers = new HashSet<>();
104 
105         try (DeviceList list = new DeviceList()) {
106             for (Pointer devicePointer : list) {
107                 // add all valid serial numbers
108                 try (UsbDevice device = connect(devicePointer)) {
109                     String serialNumber = device.getSerialNumber();
110                     if (serialNumber != null && (!aoaOnly || device.isAoaCompatible())) {
111                         serialNumbers.add(serialNumber);
112                     }
113                 }
114             }
115         }
116 
117         return serialNumbers;
118     }
119 
120     /**
121      * Find a USB device using its serial number.
122      *
123      * @param serialNumber device serial number
124      * @return USB device or {@code null} if not found
125      */
126     @Nullable
getDevice(@onnull String serialNumber)127     public UsbDevice getDevice(@Nonnull String serialNumber) {
128         try (DeviceList list = new DeviceList()) {
129             for (Pointer devicePointer : list) {
130                 // check if device has the right serial number
131                 UsbDevice device = connect(devicePointer);
132                 if (serialNumber.equals(device.getSerialNumber())) {
133                     return device;
134                 }
135                 device.close();
136             }
137         }
138 
139         // device not found
140         return null;
141     }
142 
143     @VisibleForTesting
connect(@onnull Pointer devicePointer)144     UsbDevice connect(@Nonnull Pointer devicePointer) {
145         return new UsbDevice(this, devicePointer);
146     }
147 
148     /**
149      * Wait for a USB device using its serial number.
150      *
151      * @param serialNumber device serial number
152      * @param timeout maximum time to wait for
153      * @return USB device or {@code null} if not found
154      */
155     @Nullable
getDevice(@onnull String serialNumber, @Nonnull Duration timeout)156     public UsbDevice getDevice(@Nonnull String serialNumber, @Nonnull Duration timeout) {
157         Instant start = Instant.now();
158         UsbDevice device = getDevice(serialNumber);
159 
160         while (device == null && timeout.compareTo(Duration.between(start, Instant.now())) > 0) {
161             Uninterruptibles.sleepUninterruptibly(POLL_INTERVAL.toNanos(), TimeUnit.NANOSECONDS);
162             device = getDevice(serialNumber);
163         }
164 
165         return device;
166     }
167 
168     /**
169      * Find an AOAv2-compatible device using its serial number.
170      *
171      * @param serialNumber device serial number
172      * @return AOAv2-compatible device or {@code null} if not found
173      */
174     @Nullable
getAoaDevice(@onnull String serialNumber)175     public AoaDevice getAoaDevice(@Nonnull String serialNumber) {
176         return getAoaDevice(serialNumber, Duration.ZERO);
177     }
178 
179     /**
180      * Wait for an AOAv2-compatible device using its serial number.
181      *
182      * @param serialNumber device serial number
183      * @param timeout maximum time to wait for
184      * @return AOAv2-compatible device or {@code null} if not found
185      */
186     @Nullable
getAoaDevice(@onnull String serialNumber, @Nonnull Duration timeout)187     public AoaDevice getAoaDevice(@Nonnull String serialNumber, @Nonnull Duration timeout) {
188         UsbDevice device = getDevice(serialNumber, timeout);
189         return device != null && device.isAoaCompatible() ? new AoaDevice(this, device) : null;
190     }
191 
192     /** De-initialize the USB context. */
193     @Override
close()194     public void close() {
195         if (mContext != null) {
196             mUsb.libusb_exit(mContext);
197             mContext = null;
198         }
199     }
200 
201     /** USB device pointer list. */
202     private class DeviceList implements Iterable<Pointer>, AutoCloseable {
203 
204         private final Pointer list;
205         private final int count;
206 
DeviceList()207         private DeviceList() {
208             PointerByReference list = new PointerByReference();
209             this.count = checkResult(mUsb.libusb_get_device_list(checkNotNull(mContext), list));
210             this.list = list.getValue();
211         }
212 
213         @Override
214         @Nonnull
iterator()215         public Iterator<Pointer> iterator() {
216             Pointer[] devices = list.getPointerArray(0, count);
217             return Arrays.stream(devices).iterator();
218         }
219 
220         @Override
close()221         public void close() {
222             mUsb.libusb_free_device_list(list, true);
223         }
224     }
225 }
226