• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.alsa.AlsaCardsParser;
20 import android.alsa.AlsaDevicesParser;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.hardware.usb.UsbConfiguration;
24 import android.hardware.usb.UsbConstants;
25 import android.hardware.usb.UsbDevice;
26 import android.hardware.usb.UsbEndpoint;
27 import android.hardware.usb.UsbInterface;
28 import android.media.AudioManager;
29 import android.os.Bundle;
30 import android.os.ParcelFileDescriptor;
31 import android.os.Parcelable;
32 import android.os.UserHandle;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.GuardedBy;
36 
37 import java.io.File;
38 import java.io.FileDescriptor;
39 import java.io.FileNotFoundException;
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 
44 /**
45  * UsbHostManager manages USB state in host mode.
46  */
47 public class UsbHostManager {
48     private static final String TAG = UsbHostManager.class.getSimpleName();
49     private static final boolean DEBUG_AUDIO = false;
50 
51     // contains all connected USB devices
52     private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>();
53 
54     // USB busses to exclude from USB host support
55     private final String[] mHostBlacklist;
56 
57     private final Context mContext;
58     private final Object mLock = new Object();
59 
60     private UsbDevice mNewDevice;
61     private UsbConfiguration mNewConfiguration;
62     private UsbInterface mNewInterface;
63     private ArrayList<UsbConfiguration> mNewConfigurations;
64     private ArrayList<UsbInterface> mNewInterfaces;
65     private ArrayList<UsbEndpoint> mNewEndpoints;
66 
67     // Attributes of any connected USB audio device.
68     //TODO(pmclean) When we extend to multiple, USB Audio devices, we will need to get
69     // more clever about this.
70     private int mConnectedUsbCard = -1;
71     private int mConnectedUsbDeviceNum = -1;
72     private boolean mConnectedHasPlayback = false;
73     private boolean mConnectedHasCapture = false;
74     private boolean mConnectedHasMIDI = false;
75 
76     @GuardedBy("mLock")
77     private UsbSettingsManager mCurrentSettings;
78 
UsbHostManager(Context context)79     public UsbHostManager(Context context) {
80         mContext = context;
81         mHostBlacklist = context.getResources().getStringArray(
82                 com.android.internal.R.array.config_usbHostBlacklist);
83     }
84 
setCurrentSettings(UsbSettingsManager settings)85     public void setCurrentSettings(UsbSettingsManager settings) {
86         synchronized (mLock) {
87             mCurrentSettings = settings;
88         }
89     }
90 
getCurrentSettings()91     private UsbSettingsManager getCurrentSettings() {
92         synchronized (mLock) {
93             return mCurrentSettings;
94         }
95     }
96 
isBlackListed(String deviceName)97     private boolean isBlackListed(String deviceName) {
98         int count = mHostBlacklist.length;
99         for (int i = 0; i < count; i++) {
100             if (deviceName.startsWith(mHostBlacklist[i])) {
101                 return true;
102             }
103         }
104         return false;
105     }
106 
107     /* returns true if the USB device should not be accessible by applications */
isBlackListed(int clazz, int subClass, int protocol)108     private boolean isBlackListed(int clazz, int subClass, int protocol) {
109         // blacklist hubs
110         if (clazz == UsbConstants.USB_CLASS_HUB) return true;
111 
112         // blacklist HID boot devices (mouse and keyboard)
113         if (clazz == UsbConstants.USB_CLASS_HID &&
114                 subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) {
115             return true;
116         }
117 
118         return false;
119     }
120 
121     // Broadcasts the arrival/departure of a USB audio interface
122     // card - the ALSA card number of the physical interface
123     // device - the ALSA device number of the physical interface
124     // enabled - if true, we're connecting a device (it's arrived), else disconnecting
sendDeviceNotification(int card, int device, boolean enabled, boolean hasPlayback, boolean hasCapture, boolean hasMIDI)125     private void sendDeviceNotification(int card, int device, boolean enabled,
126             boolean hasPlayback, boolean hasCapture, boolean hasMIDI) {
127         // send a sticky broadcast containing current USB state
128         Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
129         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
130         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
131         intent.putExtra("state", enabled ? 1 : 0);
132         intent.putExtra("card", card);
133         intent.putExtra("device", device);
134         intent.putExtra("hasPlayback", hasPlayback);
135         intent.putExtra("hasCapture", hasCapture);
136         intent.putExtra("hasMIDI", hasMIDI);
137         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
138     }
139 
waitForAlsaFile(int card, int device, boolean capture)140     private boolean waitForAlsaFile(int card, int device, boolean capture) {
141         // These values were empirically determined.
142         final int kNumRetries = 5;
143         final int kSleepTime = 500; // ms
144         String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
145         File alsaDevFile = new File(alsaDevPath);
146         boolean exists = false;
147         for (int retry = 0; !exists && retry < kNumRetries; retry++) {
148             exists = alsaDevFile.exists();
149             if (!exists) {
150                 try {
151                     Thread.sleep(kSleepTime);
152                 } catch (IllegalThreadStateException ex) {
153                     Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
154                 } catch (java.lang.InterruptedException ex) {
155                     Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
156                 }
157             }
158         }
159 
160         return exists;
161     }
162 
163     /* Called from JNI in monitorUsbHostBus() to report new USB devices
164        Returns true if successful, in which case the JNI code will continue adding configurations,
165        interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
166        have been processed
167      */
beginUsbDeviceAdded(String deviceName, int vendorID, int productID, int deviceClass, int deviceSubclass, int deviceProtocol, String manufacturerName, String productName, String serialNumber)168     private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
169             int deviceClass, int deviceSubclass, int deviceProtocol,
170             String manufacturerName, String productName, String serialNumber) {
171 
172         if (DEBUG_AUDIO) {
173             Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
174             // Audio Class Codes:
175             // Audio: 0x01
176             // Audio Subclass Codes:
177             // undefined: 0x00
178             // audio control: 0x01
179             // audio streaming: 0x02
180             // midi streaming: 0x03
181 
182             // some useful debugging info
183             Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
184                     + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
185         }
186 
187         // OK this is non-obvious, but true. One can't tell if the device being attached is even
188         // potentially an audio device without parsing the interface descriptors, so punt on any
189         // such test until endUsbDeviceAdded() when we have that info.
190 
191         if (isBlackListed(deviceName) ||
192                 isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
193             return false;
194         }
195 
196         synchronized (mLock) {
197             if (mDevices.get(deviceName) != null) {
198                 Slog.w(TAG, "device already on mDevices list: " + deviceName);
199                 return false;
200             }
201 
202             if (mNewDevice != null) {
203                 Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
204                 return false;
205             }
206 
207             mNewDevice = new UsbDevice(deviceName, vendorID, productID,
208                     deviceClass, deviceSubclass, deviceProtocol,
209                     manufacturerName, productName, serialNumber);
210 
211             mNewConfigurations = new ArrayList<UsbConfiguration>();
212             mNewInterfaces = new ArrayList<UsbInterface>();
213             mNewEndpoints = new ArrayList<UsbEndpoint>();
214         }
215 
216         return true;
217     }
218 
219     /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
220        currently being added.  Returns true if successful, false in case of error.
221      */
addUsbConfiguration(int id, String name, int attributes, int maxPower)222     private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
223         if (mNewConfiguration != null) {
224             mNewConfiguration.setInterfaces(
225                     mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
226             mNewInterfaces.clear();
227         }
228 
229         mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
230         mNewConfigurations.add(mNewConfiguration);
231     }
232 
233     /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
234        currently being added.  Returns true if successful, false in case of error.
235      */
addUsbInterface(int id, String name, int altSetting, int Class, int subClass, int protocol)236     private void addUsbInterface(int id, String name, int altSetting,
237             int Class, int subClass, int protocol) {
238         if (mNewInterface != null) {
239             mNewInterface.setEndpoints(
240                     mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
241             mNewEndpoints.clear();
242         }
243 
244         mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
245         mNewInterfaces.add(mNewInterface);
246     }
247 
248     /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
249        currently being added.  Returns true if successful, false in case of error.
250      */
addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval)251     private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
252         mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
253     }
254 
255     /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
endUsbDeviceAdded()256     private void endUsbDeviceAdded() {
257         if (DEBUG_AUDIO) {
258             Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
259         }
260         if (mNewInterface != null) {
261             mNewInterface.setEndpoints(
262                     mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
263         }
264         if (mNewConfiguration != null) {
265             mNewConfiguration.setInterfaces(
266                     mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
267         }
268 
269         // Is there an audio interface in there?
270         final int kUsbClassId_Audio = 0x01;
271         boolean isAudioDevice = false;
272         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < mNewInterfaces.size();
273                 ntrfaceIndex++) {
274             UsbInterface ntrface = mNewInterfaces.get(ntrfaceIndex);
275             if (ntrface.getInterfaceClass() == kUsbClassId_Audio) {
276                 isAudioDevice = true;
277             }
278         }
279 
280         synchronized (mLock) {
281             if (mNewDevice != null) {
282                 mNewDevice.setConfigurations(
283                         mNewConfigurations.toArray(new UsbConfiguration[mNewConfigurations.size()]));
284                 mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
285                 Slog.d(TAG, "Added device " + mNewDevice);
286                 getCurrentSettings().deviceAttached(mNewDevice);
287             } else {
288                 Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
289             }
290             mNewDevice = null;
291             mNewConfigurations = null;
292             mNewInterfaces = null;
293             mNewEndpoints = null;
294         }
295 
296         if (!isAudioDevice) {
297             return; // bail
298         }
299 
300         //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
301         // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
302         // clear why this works, or that it can be relied on going forward.  Needs further
303         // research.
304         AlsaCardsParser cardsParser = new AlsaCardsParser();
305         cardsParser.scan();
306         // cardsParser.Log();
307 
308         // But we need to parse the device to determine its capabilities.
309         AlsaDevicesParser devicesParser = new AlsaDevicesParser();
310         devicesParser.scan();
311         // devicesParser.Log();
312 
313         // The protocol for now will be to select the last-connected (highest-numbered)
314         // Alsa Card.
315         mConnectedUsbCard = cardsParser.getNumCardRecords() - 1;
316         mConnectedUsbDeviceNum = 0;
317 
318         mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard);
319         mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard);
320         mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard);
321 
322         // Playback device file needed/present?
323         if (mConnectedHasPlayback &&
324             !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) {
325             return;
326         }
327 
328         // Capture device file needed/present?
329         if (mConnectedHasCapture &&
330             !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, true)) {
331             return;
332         }
333 
334         if (DEBUG_AUDIO) {
335             Slog.d(TAG,
336                     "usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture);
337         }
338 
339         sendDeviceNotification(mConnectedUsbCard,
340                 mConnectedUsbDeviceNum,
341                 true,
342                 mConnectedHasPlayback,
343                 mConnectedHasCapture,
344                 mConnectedHasMIDI);
345     }
346 
347     /* Called from JNI in monitorUsbHostBus to report USB device removal */
usbDeviceRemoved(String deviceName)348     private void usbDeviceRemoved(String deviceName) {
349         if (DEBUG_AUDIO) {
350           Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName);
351         }
352 
353         if (mConnectedUsbCard != -1 && mConnectedUsbDeviceNum != -1) {
354             sendDeviceNotification(mConnectedUsbCard,
355                     mConnectedUsbDeviceNum,
356                     false,
357                     mConnectedHasPlayback,
358                     mConnectedHasCapture,
359                     mConnectedHasMIDI);
360             mConnectedUsbCard = -1;
361             mConnectedUsbDeviceNum = -1;
362             mConnectedHasPlayback = false;
363             mConnectedHasCapture = false;
364             mConnectedHasMIDI = false;
365         }
366 
367         synchronized (mLock) {
368             UsbDevice device = mDevices.remove(deviceName);
369             if (device != null) {
370                 getCurrentSettings().deviceDetached(device);
371             }
372         }
373     }
374 
systemReady()375     public void systemReady() {
376         synchronized (mLock) {
377             // Create a thread to call into native code to wait for USB host events.
378             // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
379             Runnable runnable = new Runnable() {
380                 public void run() {
381                     monitorUsbHostBus();
382                 }
383             };
384             new Thread(null, runnable, "UsbService host thread").start();
385         }
386     }
387 
388     /* Returns a list of all currently attached USB devices */
getDeviceList(Bundle devices)389     public void getDeviceList(Bundle devices) {
390         synchronized (mLock) {
391             for (String name : mDevices.keySet()) {
392                 devices.putParcelable(name, mDevices.get(name));
393             }
394         }
395     }
396 
397     /* Opens the specified USB device */
openDevice(String deviceName)398     public ParcelFileDescriptor openDevice(String deviceName) {
399         synchronized (mLock) {
400             if (isBlackListed(deviceName)) {
401                 throw new SecurityException("USB device is on a restricted bus");
402             }
403             UsbDevice device = mDevices.get(deviceName);
404             if (device == null) {
405                 // if it is not in mDevices, it either does not exist or is blacklisted
406                 throw new IllegalArgumentException(
407                         "device " + deviceName + " does not exist or is restricted");
408             }
409             getCurrentSettings().checkPermission(device);
410             return nativeOpenDevice(deviceName);
411         }
412     }
413 
dump(FileDescriptor fd, PrintWriter pw)414     public void dump(FileDescriptor fd, PrintWriter pw) {
415         synchronized (mLock) {
416             pw.println("  USB Host State:");
417             for (String name : mDevices.keySet()) {
418                 pw.println("    " + name + ": " + mDevices.get(name));
419             }
420         }
421     }
422 
monitorUsbHostBus()423     private native void monitorUsbHostBus();
nativeOpenDevice(String deviceName)424     private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
425 }
426