1 /** 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * <p>http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * <p>Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package android.car.usb.handler; 15 16 import android.content.Context; 17 import android.hardware.usb.UsbConstants; 18 import android.hardware.usb.UsbDevice; 19 import android.hardware.usb.UsbDeviceConnection; 20 import android.util.ArraySet; 21 import android.util.Log; 22 import android.util.Pair; 23 24 import java.io.IOException; 25 import java.lang.annotation.ElementType; 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.lang.annotation.Target; 29 import java.util.HashSet; 30 import java.util.Set; 31 32 final class AoapInterface { 33 /** 34 * Use Google Vendor ID when in accessory mode 35 */ 36 private static final int USB_ACCESSORY_VENDOR_ID = 0x18D1; 37 38 /** Set of all accessory mode product IDs */ 39 private static final ArraySet<Integer> USB_ACCESSORY_MODE_PRODUCT_ID = new ArraySet<>(4); 40 static { 41 USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D00); 42 USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D01); 43 USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D04); 44 USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D05); 45 } 46 47 /** 48 * Indexes for strings sent by the host via ACCESSORY_SEND_STRING 49 */ 50 public static final int ACCESSORY_STRING_MANUFACTURER = 0; 51 public static final int ACCESSORY_STRING_MODEL = 1; 52 public static final int ACCESSORY_STRING_DESCRIPTION = 2; 53 public static final int ACCESSORY_STRING_VERSION = 3; 54 public static final int ACCESSORY_STRING_URI = 4; 55 public static final int ACCESSORY_STRING_SERIAL = 5; 56 57 /** 58 * Control request for retrieving device's protocol version 59 * 60 * requestType: USB_DIR_IN | USB_TYPE_VENDOR 61 * request: ACCESSORY_GET_PROTOCOL 62 * value: 0 63 * index: 0 64 * data version number (16 bits little endian) 65 * 1 for original accessory support 66 * 2 adds HID and device to host audio support 67 */ 68 public static final int ACCESSORY_GET_PROTOCOL = 51; 69 70 /** 71 * Control request for host to send a string to the device 72 * 73 * requestType: USB_DIR_OUT | USB_TYPE_VENDOR 74 * request: ACCESSORY_SEND_STRING 75 * value: 0 76 * index: string ID 77 * data zero terminated UTF8 string 78 * 79 * The device can later retrieve these strings via the 80 * ACCESSORY_GET_STRING_* ioctls 81 */ 82 public static final int ACCESSORY_SEND_STRING = 52; 83 84 /** 85 * Control request for starting device in accessory mode. 86 * The host sends this after setting all its strings to the device. 87 * 88 * requestType: USB_DIR_OUT | USB_TYPE_VENDOR 89 * request: ACCESSORY_START 90 * value: 0 91 * index: 0 92 * data none 93 */ 94 public static final int ACCESSORY_START = 53; 95 96 /** 97 * Max payload size for AOAP. Limited by driver. 98 */ 99 public static final int MAX_PAYLOAD_SIZE = 16384; 100 101 /** 102 * Accessory write timeout. 103 */ 104 public static final int AOAP_TIMEOUT_MS = 50; 105 106 /** 107 * Set of VID:PID pairs denylisted through config_AoapIncompatibleDeviceIds. Only 108 * isDeviceDenylisted() should ever access this variable. 109 */ 110 private static Set<Pair<Integer, Integer>> sDenylistedVidPidPairs; 111 112 private static final String TAG = AoapInterface.class.getSimpleName(); 113 114 @Retention(RetentionPolicy.SOURCE) 115 @Target({ ElementType.FIELD, ElementType.PARAMETER }) 116 public @interface Direction {} 117 118 @Direction 119 public static final int WRITE = 1; 120 @Direction 121 public static final int READ = 2; 122 123 getProtocol(UsbDeviceConnection conn)124 public static int getProtocol(UsbDeviceConnection conn) { 125 byte[] buffer = new byte[2]; 126 127 int len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length); 128 if (len == 0) { 129 return -1; 130 } 131 if (len < 0) { 132 Log.w(TAG, "getProtocol() failed. Retrying..."); 133 len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length); 134 if (len != buffer.length) { 135 return -1; 136 } 137 } 138 return (buffer[1] << 8) | buffer[0]; 139 } 140 isSupported(Context context, UsbDevice device, UsbDeviceConnection conn)141 public static boolean isSupported(Context context, UsbDevice device, UsbDeviceConnection conn) { 142 return !isDeviceDenylisted(context, device) && getProtocol(conn) >= 1; 143 } 144 sendString(UsbDeviceConnection conn, int index, String string)145 public static void sendString(UsbDeviceConnection conn, int index, String string) 146 throws IOException { 147 byte[] buffer = (string + "\0").getBytes(); 148 int len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer, 149 buffer.length); 150 if (len != buffer.length) { 151 Log.w(TAG, "sendString for " + index + ":" + string + " failed. Retrying..."); 152 len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer, 153 buffer.length); 154 if (len != buffer.length) { 155 throw new IOException("Failed to send string " + index + ": \"" + string + "\""); 156 } 157 } else { 158 Log.i(TAG, "Sent string " + index + ": \"" + string + "\""); 159 } 160 } 161 sendAoapStart(UsbDeviceConnection conn)162 public static void sendAoapStart(UsbDeviceConnection conn) throws IOException { 163 int len = transfer(conn, WRITE, ACCESSORY_START, 0, null, 0); 164 if (len < 0) { 165 throw new IOException("Control transfer for accessory start failed: " + len); 166 } 167 } 168 isDeviceDenylisted(Context context, UsbDevice device)169 public static synchronized boolean isDeviceDenylisted(Context context, UsbDevice device) { 170 if (sDenylistedVidPidPairs == null) { 171 sDenylistedVidPidPairs = new HashSet<>(); 172 String[] idPairs = 173 context.getResources().getStringArray(R.array.config_AoapIncompatibleDeviceIds); 174 for (String idPair : idPairs) { 175 boolean success = false; 176 String[] tokens = idPair.split(":"); 177 if (tokens.length == 2) { 178 try { 179 sDenylistedVidPidPairs.add(Pair.create(Integer.parseInt(tokens[0], 16), 180 Integer.parseInt(tokens[1], 16))); 181 success = true; 182 } catch (NumberFormatException e) { 183 } 184 } 185 if (!success) { 186 Log.e(TAG, "config_AoapIncompatibleDeviceIds contains malformed value: " 187 + idPair); 188 } 189 } 190 } 191 192 return sDenylistedVidPidPairs.contains(Pair.create(device.getVendorId(), 193 device.getProductId())); 194 } 195 isDeviceInAoapMode(UsbDevice device)196 public static boolean isDeviceInAoapMode(UsbDevice device) { 197 if (device == null) { 198 return false; 199 } 200 final int vid = device.getVendorId(); 201 final int pid = device.getProductId(); 202 return vid == USB_ACCESSORY_VENDOR_ID 203 && USB_ACCESSORY_MODE_PRODUCT_ID.contains(pid); 204 } 205 transfer(UsbDeviceConnection conn, @Direction int direction, int string, int index, byte[] buffer, int length)206 private static int transfer(UsbDeviceConnection conn, @Direction int direction, int string, 207 int index, byte[] buffer, int length) { 208 int directionConstant; 209 switch (direction) { 210 case READ: 211 directionConstant = UsbConstants.USB_DIR_IN; 212 break; 213 case WRITE: 214 directionConstant = UsbConstants.USB_DIR_OUT; 215 break; 216 default: 217 Log.w(TAG, "Unknown direction for transfer: " + direction); 218 return -1; 219 } 220 return conn.controlTransfer(directionConstant | UsbConstants.USB_TYPE_VENDOR, string, 0, 221 index, buffer, length, AOAP_TIMEOUT_MS); 222 } 223 } 224