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