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