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