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