1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions an
14 * limitations under the License.
15 */
16
17package com.android.server.usb;
18
19import android.content.Context;
20import android.hardware.usb.UsbConfiguration;
21import android.hardware.usb.UsbConstants;
22import android.hardware.usb.UsbDevice;
23import android.hardware.usb.UsbEndpoint;
24import android.hardware.usb.UsbInterface;
25import android.os.Bundle;
26import android.os.ParcelFileDescriptor;
27import android.util.Slog;
28
29import com.android.internal.annotations.GuardedBy;
30import com.android.internal.util.IndentingPrintWriter;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.HashMap;
36
37/**
38 * UsbHostManager manages USB state in host mode.
39 */
40public class UsbHostManager {
41    private static final String TAG = UsbHostManager.class.getSimpleName();
42    private static final boolean DEBUG = false;
43
44    // contains all connected USB devices
45    private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>();
46
47
48    // USB busses to exclude from USB host support
49    private final String[] mHostBlacklist;
50
51    private final Context mContext;
52    private final Object mLock = new Object();
53
54    private UsbDevice mNewDevice;
55    private UsbConfiguration mNewConfiguration;
56    private UsbInterface mNewInterface;
57    private ArrayList<UsbConfiguration> mNewConfigurations;
58    private ArrayList<UsbInterface> mNewInterfaces;
59    private ArrayList<UsbEndpoint> mNewEndpoints;
60
61    private final UsbAlsaManager mUsbAlsaManager;
62
63    @GuardedBy("mLock")
64    private UsbSettingsManager mCurrentSettings;
65
66    public UsbHostManager(Context context, UsbAlsaManager alsaManager) {
67        mContext = context;
68        mHostBlacklist = context.getResources().getStringArray(
69                com.android.internal.R.array.config_usbHostBlacklist);
70        mUsbAlsaManager = alsaManager;
71    }
72
73    public void setCurrentSettings(UsbSettingsManager settings) {
74        synchronized (mLock) {
75            mCurrentSettings = settings;
76        }
77    }
78
79    private UsbSettingsManager getCurrentSettings() {
80        synchronized (mLock) {
81            return mCurrentSettings;
82        }
83    }
84
85    private boolean isBlackListed(String deviceName) {
86        int count = mHostBlacklist.length;
87        for (int i = 0; i < count; i++) {
88            if (deviceName.startsWith(mHostBlacklist[i])) {
89                return true;
90            }
91        }
92        return false;
93    }
94
95    /* returns true if the USB device should not be accessible by applications */
96    private boolean isBlackListed(int clazz, int subClass, int protocol) {
97        // blacklist hubs
98        if (clazz == UsbConstants.USB_CLASS_HUB) return true;
99
100        // blacklist HID boot devices (mouse and keyboard)
101        if (clazz == UsbConstants.USB_CLASS_HID &&
102                subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) {
103            return true;
104        }
105
106        return false;
107    }
108
109    /* Called from JNI in monitorUsbHostBus() to report new USB devices
110       Returns true if successful, in which case the JNI code will continue adding configurations,
111       interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
112       have been processed
113     */
114    private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
115            int deviceClass, int deviceSubclass, int deviceProtocol,
116            String manufacturerName, String productName, int version, String serialNumber) {
117
118        if (DEBUG) {
119            Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
120            // Audio Class Codes:
121            // Audio: 0x01
122            // Audio Subclass Codes:
123            // undefined: 0x00
124            // audio control: 0x01
125            // audio streaming: 0x02
126            // midi streaming: 0x03
127
128            // some useful debugging info
129            Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
130                    + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
131        }
132
133        // OK this is non-obvious, but true. One can't tell if the device being attached is even
134        // potentially an audio device without parsing the interface descriptors, so punt on any
135        // such test until endUsbDeviceAdded() when we have that info.
136
137        if (isBlackListed(deviceName) ||
138                isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
139            return false;
140        }
141
142        synchronized (mLock) {
143            if (mDevices.get(deviceName) != null) {
144                Slog.w(TAG, "device already on mDevices list: " + deviceName);
145                return false;
146            }
147
148            if (mNewDevice != null) {
149                Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
150                return false;
151            }
152
153            // Create version string in "%.%" format
154            String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
155
156            mNewDevice = new UsbDevice(deviceName, vendorID, productID,
157                    deviceClass, deviceSubclass, deviceProtocol,
158                    manufacturerName, productName, versionString, serialNumber);
159
160            mNewConfigurations = new ArrayList<UsbConfiguration>();
161            mNewInterfaces = new ArrayList<UsbInterface>();
162            mNewEndpoints = new ArrayList<UsbEndpoint>();
163        }
164
165        return true;
166    }
167
168    /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
169       currently being added.  Returns true if successful, false in case of error.
170     */
171    private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
172        if (mNewConfiguration != null) {
173            mNewConfiguration.setInterfaces(
174                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
175            mNewInterfaces.clear();
176        }
177
178        mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
179        mNewConfigurations.add(mNewConfiguration);
180    }
181
182    /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
183       currently being added.  Returns true if successful, false in case of error.
184     */
185    private void addUsbInterface(int id, String name, int altSetting,
186            int Class, int subClass, int protocol) {
187        if (mNewInterface != null) {
188            mNewInterface.setEndpoints(
189                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
190            mNewEndpoints.clear();
191        }
192
193        mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
194        mNewInterfaces.add(mNewInterface);
195    }
196
197    /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
198       currently being added.  Returns true if successful, false in case of error.
199     */
200    private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
201        mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
202    }
203
204    /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
205    private void endUsbDeviceAdded() {
206        if (DEBUG) {
207            Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
208        }
209        if (mNewInterface != null) {
210            mNewInterface.setEndpoints(
211                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
212        }
213        if (mNewConfiguration != null) {
214            mNewConfiguration.setInterfaces(
215                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
216        }
217
218
219        synchronized (mLock) {
220            if (mNewDevice != null) {
221                mNewDevice.setConfigurations(
222                        mNewConfigurations.toArray(new UsbConfiguration[mNewConfigurations.size()]));
223                mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
224                Slog.d(TAG, "Added device " + mNewDevice);
225                getCurrentSettings().deviceAttached(mNewDevice);
226                mUsbAlsaManager.usbDeviceAdded(mNewDevice);
227            } else {
228                Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
229            }
230            mNewDevice = null;
231            mNewConfigurations = null;
232            mNewInterfaces = null;
233            mNewEndpoints = null;
234            mNewConfiguration = null;
235            mNewInterface = null;
236        }
237    }
238
239    /* Called from JNI in monitorUsbHostBus to report USB device removal */
240    private void usbDeviceRemoved(String deviceName) {
241        synchronized (mLock) {
242            UsbDevice device = mDevices.remove(deviceName);
243            if (device != null) {
244                mUsbAlsaManager.usbDeviceRemoved(device);
245                getCurrentSettings().deviceDetached(device);
246            }
247        }
248    }
249
250    public void systemReady() {
251        synchronized (mLock) {
252            // Create a thread to call into native code to wait for USB host events.
253            // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
254            Runnable runnable = new Runnable() {
255                public void run() {
256                    monitorUsbHostBus();
257                }
258            };
259            new Thread(null, runnable, "UsbService host thread").start();
260        }
261    }
262
263    /* Returns a list of all currently attached USB devices */
264    public void getDeviceList(Bundle devices) {
265        synchronized (mLock) {
266            for (String name : mDevices.keySet()) {
267                devices.putParcelable(name, mDevices.get(name));
268            }
269        }
270    }
271
272    /* Opens the specified USB device */
273    public ParcelFileDescriptor openDevice(String deviceName) {
274        synchronized (mLock) {
275            if (isBlackListed(deviceName)) {
276                throw new SecurityException("USB device is on a restricted bus");
277            }
278            UsbDevice device = mDevices.get(deviceName);
279            if (device == null) {
280                // if it is not in mDevices, it either does not exist or is blacklisted
281                throw new IllegalArgumentException(
282                        "device " + deviceName + " does not exist or is restricted");
283            }
284            getCurrentSettings().checkPermission(device);
285            return nativeOpenDevice(deviceName);
286        }
287    }
288
289    public void dump(IndentingPrintWriter pw) {
290        synchronized (mLock) {
291            pw.println("USB Host State:");
292            for (String name : mDevices.keySet()) {
293                pw.println("  " + name + ": " + mDevices.get(name));
294            }
295        }
296    }
297
298    private native void monitorUsbHostBus();
299    private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
300}
301