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