/** * Copyright (C) 2016 The Android Open Source Project * *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * *

http://www.apache.org/licenses/LICENSE-2.0 * *

Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package android.car.usb.handler; import android.content.Context; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.HashSet; import java.util.Set; final class AoapInterface { /** * Use Google Vendor ID when in accessory mode */ private static final int USB_ACCESSORY_VENDOR_ID = 0x18D1; /** Set of all accessory mode product IDs */ private static final ArraySet USB_ACCESSORY_MODE_PRODUCT_ID = new ArraySet<>(4); static { USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D00); USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D01); USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D04); USB_ACCESSORY_MODE_PRODUCT_ID.add(0x2D05); } /** * Indexes for strings sent by the host via ACCESSORY_SEND_STRING */ public static final int ACCESSORY_STRING_MANUFACTURER = 0; public static final int ACCESSORY_STRING_MODEL = 1; public static final int ACCESSORY_STRING_DESCRIPTION = 2; public static final int ACCESSORY_STRING_VERSION = 3; public static final int ACCESSORY_STRING_URI = 4; public static final int ACCESSORY_STRING_SERIAL = 5; /** * Control request for retrieving device's protocol version * * requestType: USB_DIR_IN | USB_TYPE_VENDOR * request: ACCESSORY_GET_PROTOCOL * value: 0 * index: 0 * data version number (16 bits little endian) * 1 for original accessory support * 2 adds HID and device to host audio support */ public static final int ACCESSORY_GET_PROTOCOL = 51; /** * Control request for host to send a string to the device * * requestType: USB_DIR_OUT | USB_TYPE_VENDOR * request: ACCESSORY_SEND_STRING * value: 0 * index: string ID * data zero terminated UTF8 string * * The device can later retrieve these strings via the * ACCESSORY_GET_STRING_* ioctls */ public static final int ACCESSORY_SEND_STRING = 52; /** * Control request for starting device in accessory mode. * The host sends this after setting all its strings to the device. * * requestType: USB_DIR_OUT | USB_TYPE_VENDOR * request: ACCESSORY_START * value: 0 * index: 0 * data none */ public static final int ACCESSORY_START = 53; /** * Max payload size for AOAP. Limited by driver. */ public static final int MAX_PAYLOAD_SIZE = 16384; /** * Accessory write timeout. */ public static final int AOAP_TIMEOUT_MS = 50; /** * Set of VID:PID pairs denylisted through config_AoapIncompatibleDeviceIds. Only * isDeviceDenylisted() should ever access this variable. */ private static Set> sDenylistedVidPidPairs; private static final String TAG = AoapInterface.class.getSimpleName(); @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.FIELD, ElementType.PARAMETER }) public @interface Direction {} @Direction public static final int WRITE = 1; @Direction public static final int READ = 2; public static int getProtocol(UsbDeviceConnection conn) { byte[] buffer = new byte[2]; int len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length); if (len == 0) { return -1; } if (len < 0) { Log.w(TAG, "getProtocol() failed. Retrying..."); len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length); if (len != buffer.length) { return -1; } } return (buffer[1] << 8) | buffer[0]; } public static boolean isSupported(Context context, UsbDevice device, UsbDeviceConnection conn) { return !isDeviceDenylisted(context, device) && getProtocol(conn) >= 1; } public static void sendString(UsbDeviceConnection conn, int index, String string) throws IOException { byte[] buffer = (string + "\0").getBytes(); int len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer, buffer.length); if (len != buffer.length) { Log.w(TAG, "sendString for " + index + ":" + string + " failed. Retrying..."); len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer, buffer.length); if (len != buffer.length) { throw new IOException("Failed to send string " + index + ": \"" + string + "\""); } } else { Log.i(TAG, "Sent string " + index + ": \"" + string + "\""); } } public static void sendAoapStart(UsbDeviceConnection conn) throws IOException { int len = transfer(conn, WRITE, ACCESSORY_START, 0, null, 0); if (len < 0) { throw new IOException("Control transfer for accessory start failed: " + len); } } public static synchronized boolean isDeviceDenylisted(Context context, UsbDevice device) { if (sDenylistedVidPidPairs == null) { sDenylistedVidPidPairs = new HashSet<>(); String[] idPairs = context.getResources().getStringArray(R.array.config_AoapIncompatibleDeviceIds); for (String idPair : idPairs) { boolean success = false; String[] tokens = idPair.split(":"); if (tokens.length == 2) { try { sDenylistedVidPidPairs.add(Pair.create(Integer.parseInt(tokens[0], 16), Integer.parseInt(tokens[1], 16))); success = true; } catch (NumberFormatException e) { } } if (!success) { Log.e(TAG, "config_AoapIncompatibleDeviceIds contains malformed value: " + idPair); } } } return sDenylistedVidPidPairs.contains(Pair.create(device.getVendorId(), device.getProductId())); } public static boolean isDeviceInAoapMode(UsbDevice device) { if (device == null) { return false; } final int vid = device.getVendorId(); final int pid = device.getProductId(); return vid == USB_ACCESSORY_VENDOR_ID && USB_ACCESSORY_MODE_PRODUCT_ID.contains(pid); } private static int transfer(UsbDeviceConnection conn, @Direction int direction, int string, int index, byte[] buffer, int length) { int directionConstant; switch (direction) { case READ: directionConstant = UsbConstants.USB_DIR_IN; break; case WRITE: directionConstant = UsbConstants.USB_DIR_OUT; break; default: Log.w(TAG, "Unknown direction for transfer: " + direction); return -1; } return conn.controlTransfer(directionConstant | UsbConstants.USB_TYPE_VENDOR, string, 0, index, buffer, length, AOAP_TIMEOUT_MS); } }