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 
17 package com.android.server.usb;
18 
19 import android.content.Context;
20 import android.hardware.usb.UsbConfiguration;
21 import android.hardware.usb.UsbConstants;
22 import android.hardware.usb.UsbDevice;
23 import android.hardware.usb.UsbEndpoint;
24 import android.hardware.usb.UsbInterface;
25 import android.os.Bundle;
26 import android.os.ParcelFileDescriptor;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.util.IndentingPrintWriter;
31 
32 import java.io.FileDescriptor;
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 
37 /**
38  * UsbHostManager manages USB state in host mode.
39  */
40 public 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 
UsbHostManager(Context context, UsbAlsaManager alsaManager)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 
setCurrentSettings(UsbSettingsManager settings)73     public void setCurrentSettings(UsbSettingsManager settings) {
74         synchronized (mLock) {
75             mCurrentSettings = settings;
76         }
77     }
78 
getCurrentSettings()79     private UsbSettingsManager getCurrentSettings() {
80         synchronized (mLock) {
81             return mCurrentSettings;
82         }
83     }
84 
isBlackListed(String deviceName)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 */
isBlackListed(int clazz, int subClass, int protocol)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      */
beginUsbDeviceAdded(String deviceName, int vendorID, int productID, int deviceClass, int deviceSubclass, int deviceProtocol, String manufacturerName, String productName, int version, String serialNumber)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      */
addUsbConfiguration(int id, String name, int attributes, int maxPower)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      */
addUsbInterface(int id, String name, int altSetting, int Class, int subClass, int protocol)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      */
addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval)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 */
endUsbDeviceAdded()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 */
usbDeviceRemoved(String deviceName)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 
systemReady()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 */
getDeviceList(Bundle 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 */
openDevice(String deviceName)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 
dump(IndentingPrintWriter pw)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 
monitorUsbHostBus()298     private native void monitorUsbHostBus();
nativeOpenDevice(String deviceName)299     private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
300 }
301