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