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