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