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