• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     private static final Duration POLL_INTERVAL = Duration.ofSeconds(1L);
46 
47     private final IUsbNative mUsb;
48     private Pointer mContext;
49 
UsbHelper()50     public UsbHelper() {
51         this((IUsbNative) Native.loadLibrary("usb-1.0", IUsbNative.class));
52     }
53 
54     @VisibleForTesting
UsbHelper(@onnull IUsbNative usb)55     UsbHelper(@Nonnull IUsbNative usb) {
56         mUsb = usb;
57         // initialize context
58         PointerByReference context = new PointerByReference();
59         checkResult(mUsb.libusb_init(context));
60         mContext = context.getValue();
61     }
62 
63     /**
64      * Verifies a USB response, throwing an exception if it corresponds to an error.
65      *
66      * @param value result or error code
67      */
checkResult(int value)68     public int checkResult(int value) {
69         if (value < 0) {
70             throw new UsbException(mUsb.libusb_strerror(value));
71         }
72         return value;
73     }
74 
75     /**
76      * Find all connected device serial numbers.
77      *
78      * @param aoaOnly whether to only include AOAv2-compatible devices
79      * @return USB device serial numbers
80      */
81     @Nonnull
getSerialNumbers(boolean aoaOnly)82     public Set<String> getSerialNumbers(boolean aoaOnly) {
83         Set<String> serialNumbers = new HashSet<>();
84 
85         try (DeviceList list = new DeviceList()) {
86             for (Pointer devicePointer : list) {
87                 // add all valid serial numbers
88                 try (UsbDevice device = connect(devicePointer)) {
89                     String serialNumber = device.getSerialNumber();
90                     if (serialNumber != null && (!aoaOnly || device.isAoaCompatible())) {
91                         serialNumbers.add(serialNumber);
92                     }
93                 }
94             }
95         }
96 
97         return serialNumbers;
98     }
99 
100     /**
101      * Find a USB device using its serial number.
102      *
103      * @param serialNumber device serial number
104      * @return USB device or {@code null} if not found
105      */
106     @Nullable
getDevice(@onnull String serialNumber)107     public UsbDevice getDevice(@Nonnull String serialNumber) {
108         try (DeviceList list = new DeviceList()) {
109             for (Pointer devicePointer : list) {
110                 // check if device has the right serial number
111                 UsbDevice device = connect(devicePointer);
112                 if (serialNumber.equals(device.getSerialNumber())) {
113                     return device;
114                 }
115                 device.close();
116             }
117         }
118 
119         // device not found
120         return null;
121     }
122 
123     @VisibleForTesting
connect(@onnull Pointer devicePointer)124     UsbDevice connect(@Nonnull Pointer devicePointer) {
125         return new UsbDevice(mUsb, devicePointer);
126     }
127 
128     /**
129      * Wait for a USB device using its serial number.
130      *
131      * @param serialNumber device serial number
132      * @param timeout maximum time to wait for
133      * @return USB device or {@code null} if not found
134      */
135     @Nullable
getDevice(@onnull String serialNumber, @Nonnull Duration timeout)136     public UsbDevice getDevice(@Nonnull String serialNumber, @Nonnull Duration timeout) {
137         Instant start = Instant.now();
138         UsbDevice device = getDevice(serialNumber);
139 
140         while (device == null && timeout.compareTo(Duration.between(start, Instant.now())) > 0) {
141             Uninterruptibles.sleepUninterruptibly(POLL_INTERVAL.toNanos(), TimeUnit.NANOSECONDS);
142             device = getDevice(serialNumber);
143         }
144 
145         return device;
146     }
147 
148     /**
149      * Find an AOAv2-compatible device using its serial number.
150      *
151      * @param serialNumber device serial number
152      * @return AOAv2-compatible device or {@code null} if not found
153      */
154     @Nullable
getAoaDevice(@onnull String serialNumber)155     public AoaDevice getAoaDevice(@Nonnull String serialNumber) {
156         return getAoaDevice(serialNumber, Duration.ZERO);
157     }
158 
159     /**
160      * Wait for an AOAv2-compatible device using its serial number.
161      *
162      * @param serialNumber device serial number
163      * @param timeout maximum time to wait for
164      * @return AOAv2-compatible device or {@code null} if not found
165      */
166     @Nullable
getAoaDevice(@onnull String serialNumber, @Nonnull Duration timeout)167     public AoaDevice getAoaDevice(@Nonnull String serialNumber, @Nonnull Duration timeout) {
168         UsbDevice device = getDevice(serialNumber, timeout);
169         return device != null && device.isAoaCompatible() ? new AoaDevice(this, device) : null;
170     }
171 
172     /** De-initialize the USB context. */
173     @Override
close()174     public void close() {
175         if (mContext != null) {
176             mUsb.libusb_exit(mContext);
177             mContext = null;
178         }
179     }
180 
181     /** USB device pointer list. */
182     private class DeviceList implements Iterable<Pointer>, AutoCloseable {
183 
184         private final Pointer list;
185         private final int count;
186 
DeviceList()187         private DeviceList() {
188             PointerByReference list = new PointerByReference();
189             this.count = checkResult(mUsb.libusb_get_device_list(checkNotNull(mContext), list));
190             this.list = list.getValue();
191         }
192 
193         @Override
194         @Nonnull
iterator()195         public Iterator<Pointer> iterator() {
196             Pointer[] devices = list.getPointerArray(0, count);
197             return Arrays.stream(devices).iterator();
198         }
199 
200         @Override
close()201         public void close() {
202             mUsb.libusb_free_device_list(list, true);
203         }
204     }
205 }
206