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