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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 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.os.Bundle; 29 import android.os.ParcelFileDescriptor; 30 import android.text.TextUtils; 31 import android.util.Slog; 32 33 import com.android.internal.annotations.GuardedBy; 34 import com.android.internal.util.IndentingPrintWriter; 35 36 import java.util.ArrayList; 37 import java.util.HashMap; 38 39 /** 40 * UsbHostManager manages USB state in host mode. 41 */ 42 public 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 final UsbAlsaManager mUsbAlsaManager; 64 private final UsbSettingsManager mSettingsManager; 65 66 @GuardedBy("mLock") 67 private UsbProfileGroupSettingsManager mCurrentSettings; 68 69 @GuardedBy("mLock") 70 private ComponentName mUsbDeviceConnectionHandler; 71 72 public UsbHostManager(Context context, UsbAlsaManager alsaManager, 73 UsbSettingsManager settingsManager) { 74 mContext = context; 75 mHostBlacklist = context.getResources().getStringArray( 76 com.android.internal.R.array.config_usbHostBlacklist); 77 mUsbAlsaManager = alsaManager; 78 mSettingsManager = settingsManager; 79 String deviceConnectionHandler = context.getResources().getString( 80 com.android.internal.R.string.config_UsbDeviceConnectionHandling_component); 81 if (!TextUtils.isEmpty(deviceConnectionHandler)) { 82 setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( 83 deviceConnectionHandler)); 84 } 85 } 86 87 public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { 88 synchronized (mLock) { 89 mCurrentSettings = settings; 90 } 91 } 92 93 private UsbProfileGroupSettingsManager getCurrentUserSettings() { 94 synchronized (mLock) { 95 return mCurrentSettings; 96 } 97 } 98 99 public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { 100 synchronized (mLock) { 101 mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; 102 } 103 } 104 105 private @Nullable ComponentName getUsbDeviceConnectionHandler() { 106 synchronized (mLock) { 107 return mUsbDeviceConnectionHandler; 108 } 109 } 110 111 private boolean isBlackListed(String deviceName) { 112 int count = mHostBlacklist.length; 113 for (int i = 0; i < count; i++) { 114 if (deviceName.startsWith(mHostBlacklist[i])) { 115 return true; 116 } 117 } 118 return false; 119 } 120 121 /* returns true if the USB device should not be accessible by applications */ 122 private boolean isBlackListed(int clazz, int subClass, int protocol) { 123 // blacklist hubs 124 if (clazz == UsbConstants.USB_CLASS_HUB) return true; 125 126 // blacklist HID boot devices (mouse and keyboard) 127 if (clazz == UsbConstants.USB_CLASS_HID && 128 subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) { 129 return true; 130 } 131 132 return false; 133 } 134 135 /* Called from JNI in monitorUsbHostBus() to report new USB devices 136 Returns true if successful, in which case the JNI code will continue adding configurations, 137 interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors 138 have been processed 139 */ 140 private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID, 141 int deviceClass, int deviceSubclass, int deviceProtocol, 142 String manufacturerName, String productName, int version, String serialNumber) { 143 144 if (DEBUG) { 145 Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")"); 146 // Audio Class Codes: 147 // Audio: 0x01 148 // Audio Subclass Codes: 149 // undefined: 0x00 150 // audio control: 0x01 151 // audio streaming: 0x02 152 // midi streaming: 0x03 153 154 // some useful debugging info 155 Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:" 156 + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol); 157 } 158 159 // OK this is non-obvious, but true. One can't tell if the device being attached is even 160 // potentially an audio device without parsing the interface descriptors, so punt on any 161 // such test until endUsbDeviceAdded() when we have that info. 162 163 if (isBlackListed(deviceName) || 164 isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) { 165 return false; 166 } 167 168 synchronized (mLock) { 169 if (mDevices.get(deviceName) != null) { 170 Slog.w(TAG, "device already on mDevices list: " + deviceName); 171 return false; 172 } 173 174 if (mNewDevice != null) { 175 Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded"); 176 return false; 177 } 178 179 // Create version string in "%.%" format 180 String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF); 181 182 mNewDevice = new UsbDevice(deviceName, vendorID, productID, 183 deviceClass, deviceSubclass, deviceProtocol, 184 manufacturerName, productName, versionString, serialNumber); 185 186 mNewConfigurations = new ArrayList<UsbConfiguration>(); 187 mNewInterfaces = new ArrayList<UsbInterface>(); 188 mNewEndpoints = new ArrayList<UsbEndpoint>(); 189 } 190 191 return true; 192 } 193 194 /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device 195 currently being added. Returns true if successful, false in case of error. 196 */ 197 private void addUsbConfiguration(int id, String name, int attributes, int maxPower) { 198 if (mNewConfiguration != null) { 199 mNewConfiguration.setInterfaces( 200 mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); 201 mNewInterfaces.clear(); 202 } 203 204 mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower); 205 mNewConfigurations.add(mNewConfiguration); 206 } 207 208 /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device 209 currently being added. Returns true if successful, false in case of error. 210 */ 211 private void addUsbInterface(int id, String name, int altSetting, 212 int Class, int subClass, int protocol) { 213 if (mNewInterface != null) { 214 mNewInterface.setEndpoints( 215 mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); 216 mNewEndpoints.clear(); 217 } 218 219 mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol); 220 mNewInterfaces.add(mNewInterface); 221 } 222 223 /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device 224 currently being added. Returns true if successful, false in case of error. 225 */ 226 private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) { 227 mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval)); 228 } 229 230 /* Called from JNI in monitorUsbHostBus() to finish adding a new device */ 231 private void endUsbDeviceAdded() { 232 if (DEBUG) { 233 Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()"); 234 } 235 if (mNewInterface != null) { 236 mNewInterface.setEndpoints( 237 mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); 238 } 239 if (mNewConfiguration != null) { 240 mNewConfiguration.setInterfaces( 241 mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); 242 } 243 244 245 synchronized (mLock) { 246 if (mNewDevice != null) { 247 mNewDevice.setConfigurations( 248 mNewConfigurations.toArray( 249 new UsbConfiguration[mNewConfigurations.size()])); 250 mDevices.put(mNewDevice.getDeviceName(), mNewDevice); 251 Slog.d(TAG, "Added device " + mNewDevice); 252 253 // It is fine to call this only for the current user as all broadcasts are sent to 254 // all profiles of the user and the dialogs should only show once. 255 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); 256 if (usbDeviceConnectionHandler == null) { 257 getCurrentUserSettings().deviceAttached(mNewDevice); 258 } else { 259 getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice, 260 usbDeviceConnectionHandler); 261 } 262 mUsbAlsaManager.usbDeviceAdded(mNewDevice); 263 } else { 264 Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); 265 } 266 mNewDevice = null; 267 mNewConfigurations = null; 268 mNewInterfaces = null; 269 mNewEndpoints = null; 270 mNewConfiguration = null; 271 mNewInterface = null; 272 } 273 } 274 275 /* Called from JNI in monitorUsbHostBus to report USB device removal */ 276 private void usbDeviceRemoved(String deviceName) { 277 synchronized (mLock) { 278 UsbDevice device = mDevices.remove(deviceName); 279 if (device != null) { 280 mUsbAlsaManager.usbDeviceRemoved(device); 281 mSettingsManager.usbDeviceRemoved(device); 282 getCurrentUserSettings().usbDeviceRemoved(device); 283 } 284 } 285 } 286 287 public void systemReady() { 288 synchronized (mLock) { 289 // Create a thread to call into native code to wait for USB host events. 290 // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. 291 Runnable runnable = new Runnable() { 292 public void run() { 293 monitorUsbHostBus(); 294 } 295 }; 296 new Thread(null, runnable, "UsbService host thread").start(); 297 } 298 } 299 300 /* Returns a list of all currently attached USB devices */ 301 public void getDeviceList(Bundle devices) { 302 synchronized (mLock) { 303 for (String name : mDevices.keySet()) { 304 devices.putParcelable(name, mDevices.get(name)); 305 } 306 } 307 } 308 309 /* Opens the specified USB device */ 310 public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) { 311 synchronized (mLock) { 312 if (isBlackListed(deviceName)) { 313 throw new SecurityException("USB device is on a restricted bus"); 314 } 315 UsbDevice device = mDevices.get(deviceName); 316 if (device == null) { 317 // if it is not in mDevices, it either does not exist or is blacklisted 318 throw new IllegalArgumentException( 319 "device " + deviceName + " does not exist or is restricted"); 320 } 321 settings.checkPermission(device); 322 return nativeOpenDevice(deviceName); 323 } 324 } 325 326 public void dump(IndentingPrintWriter pw) { 327 synchronized (mLock) { 328 pw.println("USB Host State:"); 329 for (String name : mDevices.keySet()) { 330 pw.println(" " + name + ": " + mDevices.get(name)); 331 } 332 if (mUsbDeviceConnectionHandler != null) { 333 pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); 334 } 335 } 336 } 337 338 private native void monitorUsbHostBus(); 339 private native ParcelFileDescriptor nativeOpenDevice(String deviceName); 340 } 341