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 static com.android.internal.usb.DumpUtils.writeDevice; 20 import static com.android.internal.util.dump.DumpUtils.writeComponentName; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.hardware.usb.UsbConstants; 27 import android.hardware.usb.UsbDevice; 28 import android.os.Bundle; 29 import android.os.ParcelFileDescriptor; 30 import android.service.ServiceProtoEnums; 31 import android.service.usb.UsbConnectionRecordProto; 32 import android.service.usb.UsbHostManagerProto; 33 import android.service.usb.UsbIsHeadsetProto; 34 import android.text.TextUtils; 35 import android.util.Slog; 36 37 import com.android.internal.annotations.GuardedBy; 38 import com.android.internal.util.IndentingPrintWriter; 39 import com.android.internal.util.dump.DualDumpOutputStream; 40 import com.android.server.usb.descriptors.UsbDescriptor; 41 import com.android.server.usb.descriptors.UsbDescriptorParser; 42 import com.android.server.usb.descriptors.UsbDeviceDescriptor; 43 import com.android.server.usb.descriptors.report.TextReportCanvas; 44 import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; 45 46 import java.text.SimpleDateFormat; 47 import java.util.Date; 48 import java.util.HashMap; 49 import java.util.LinkedList; 50 51 /** 52 * UsbHostManager manages USB state in host mode. 53 */ 54 public class UsbHostManager { 55 private static final String TAG = UsbHostManager.class.getSimpleName(); 56 private static final boolean DEBUG = false; 57 private static final int LINUX_FOUNDATION_VID = 0x1d6b; 58 59 private final Context mContext; 60 61 // USB busses to exclude from USB host support 62 private final String[] mHostBlacklist; 63 64 private final UsbAlsaManager mUsbAlsaManager; 65 private final UsbSettingsManager mSettingsManager; 66 67 private final Object mLock = new Object(); 68 @GuardedBy("mLock") 69 // contains all connected USB devices 70 private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); 71 72 private Object mSettingsLock = new Object(); 73 @GuardedBy("mSettingsLock") 74 private UsbProfileGroupSettingsManager mCurrentSettings; 75 76 private Object mHandlerLock = new Object(); 77 @GuardedBy("mHandlerLock") 78 private ComponentName mUsbDeviceConnectionHandler; 79 80 /* 81 * Member used for tracking connections & disconnections 82 */ 83 static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS"); 84 private static final int MAX_CONNECT_RECORDS = 32; 85 private int mNumConnects; // TOTAL # of connect/disconnect 86 private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>(); 87 private ConnectionRecord mLastConnect; 88 89 /* 90 * ConnectionRecord 91 * Stores connection/disconnection data. 92 */ 93 class ConnectionRecord { 94 long mTimestamp; // Same time-base as system log. 95 String mDeviceAddress; 96 97 static final int CONNECT = ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT; // 0 98 static final int CONNECT_BADPARSE = 99 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADPARSE; // 1 100 static final int CONNECT_BADDEVICE = 101 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADDEVICE; // 2 102 static final int DISCONNECT = 103 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_DISCONNECT; // -1 104 105 final int mMode; 106 final byte[] mDescriptors; 107 108 ConnectionRecord(String deviceAddress, int mode, byte[] descriptors) { 109 mTimestamp = System.currentTimeMillis(); 110 mDeviceAddress = deviceAddress; 111 mMode = mode; 112 mDescriptors = descriptors; 113 } 114 115 private String formatTime() { 116 return (new StringBuilder(sFormat.format(new Date(mTimestamp)))).toString(); 117 } 118 119 void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { 120 long token = dump.start(idName, id); 121 122 dump.write("device_address", UsbConnectionRecordProto.DEVICE_ADDRESS, mDeviceAddress); 123 dump.write("mode", UsbConnectionRecordProto.MODE, mMode); 124 dump.write("timestamp", UsbConnectionRecordProto.TIMESTAMP, mTimestamp); 125 126 if (mMode != DISCONNECT) { 127 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); 128 129 UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor(); 130 131 dump.write("manufacturer", UsbConnectionRecordProto.MANUFACTURER, 132 deviceDescriptor.getVendorID()); 133 dump.write("product", UsbConnectionRecordProto.PRODUCT, 134 deviceDescriptor.getProductID()); 135 long isHeadSetToken = dump.start("is_headset", UsbConnectionRecordProto.IS_HEADSET); 136 dump.write("in", UsbIsHeadsetProto.IN, parser.isInputHeadset()); 137 dump.write("out", UsbIsHeadsetProto.OUT, parser.isOutputHeadset()); 138 dump.end(isHeadSetToken); 139 } 140 141 dump.end(token); 142 } 143 144 void dumpShort(IndentingPrintWriter pw) { 145 if (mMode != DISCONNECT) { 146 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); 147 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); 148 149 UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor(); 150 151 pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID()) 152 + " product:" + Integer.toHexString(deviceDescriptor.getProductID())); 153 pw.println("isHeadset[in: " + parser.isInputHeadset() 154 + " , out: " + parser.isOutputHeadset() + "]"); 155 } else { 156 pw.println(formatTime() + " Disconnect " + mDeviceAddress); 157 } 158 } 159 160 void dumpTree(IndentingPrintWriter pw) { 161 if (mMode != DISCONNECT) { 162 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); 163 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); 164 StringBuilder stringBuilder = new StringBuilder(); 165 UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); 166 descriptorTree.parse(parser); 167 descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); 168 169 stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() 170 + " , out: " + parser.isOutputHeadset() + "]"); 171 pw.println(stringBuilder.toString()); 172 } else { 173 pw.println(formatTime() + " Disconnect " + mDeviceAddress); 174 } 175 } 176 177 void dumpList(IndentingPrintWriter pw) { 178 if (mMode != DISCONNECT) { 179 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); 180 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); 181 StringBuilder stringBuilder = new StringBuilder(); 182 TextReportCanvas canvas = new TextReportCanvas(parser, stringBuilder); 183 for (UsbDescriptor descriptor : parser.getDescriptors()) { 184 descriptor.report(canvas); 185 } 186 pw.println(stringBuilder.toString()); 187 188 pw.println("isHeadset[in: " + parser.isInputHeadset() 189 + " , out: " + parser.isOutputHeadset() + "]"); 190 } else { 191 pw.println(formatTime() + " Disconnect " + mDeviceAddress); 192 } 193 } 194 195 private static final int kDumpBytesPerLine = 16; 196 197 void dumpRaw(IndentingPrintWriter pw) { 198 if (mMode != DISCONNECT) { 199 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); 200 int length = mDescriptors.length; 201 pw.println("Raw Descriptors " + length + " bytes"); 202 int dataOffset = 0; 203 for (int line = 0; line < length / kDumpBytesPerLine; line++) { 204 StringBuilder sb = new StringBuilder(); 205 for (int offset = 0; offset < kDumpBytesPerLine; offset++) { 206 sb.append("0x") 207 .append(String.format("0x%02X", mDescriptors[dataOffset++])) 208 .append(" "); 209 } 210 pw.println(sb.toString()); 211 } 212 213 // remainder 214 StringBuilder sb = new StringBuilder(); 215 while (dataOffset < length) { 216 sb.append("0x") 217 .append(String.format("0x%02X", mDescriptors[dataOffset++])) 218 .append(" "); 219 } 220 pw.println(sb.toString()); 221 } else { 222 pw.println(formatTime() + " Disconnect " + mDeviceAddress); 223 } 224 } 225 } 226 227 /* 228 * UsbHostManager 229 */ 230 public UsbHostManager(Context context, UsbAlsaManager alsaManager, 231 UsbSettingsManager settingsManager) { 232 mContext = context; 233 234 mHostBlacklist = context.getResources().getStringArray( 235 com.android.internal.R.array.config_usbHostBlacklist); 236 mUsbAlsaManager = alsaManager; 237 mSettingsManager = settingsManager; 238 String deviceConnectionHandler = context.getResources().getString( 239 com.android.internal.R.string.config_UsbDeviceConnectionHandling_component); 240 if (!TextUtils.isEmpty(deviceConnectionHandler)) { 241 setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( 242 deviceConnectionHandler)); 243 } 244 } 245 246 public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { 247 synchronized (mSettingsLock) { 248 mCurrentSettings = settings; 249 } 250 } 251 252 private UsbProfileGroupSettingsManager getCurrentUserSettings() { 253 synchronized (mSettingsLock) { 254 return mCurrentSettings; 255 } 256 } 257 258 public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { 259 synchronized (mHandlerLock) { 260 mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; 261 } 262 } 263 264 private @Nullable ComponentName getUsbDeviceConnectionHandler() { 265 synchronized (mHandlerLock) { 266 return mUsbDeviceConnectionHandler; 267 } 268 } 269 270 private boolean isBlackListed(String deviceAddress) { 271 int count = mHostBlacklist.length; 272 for (int i = 0; i < count; i++) { 273 if (deviceAddress.startsWith(mHostBlacklist[i])) { 274 return true; 275 } 276 } 277 return false; 278 } 279 280 /* returns true if the USB device should not be accessible by applications */ 281 private boolean isBlackListed(int clazz, int subClass) { 282 // blacklist hubs 283 if (clazz == UsbConstants.USB_CLASS_HUB) return true; 284 285 // blacklist HID boot devices (mouse and keyboard) 286 return clazz == UsbConstants.USB_CLASS_HID 287 && subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT; 288 289 } 290 291 private void addConnectionRecord(String deviceAddress, int mode, byte[] rawDescriptors) { 292 mNumConnects++; 293 while (mConnections.size() >= MAX_CONNECT_RECORDS) { 294 mConnections.removeFirst(); 295 } 296 ConnectionRecord rec = 297 new ConnectionRecord(deviceAddress, mode, rawDescriptors); 298 mConnections.add(rec); 299 if (mode != ConnectionRecord.DISCONNECT) { 300 mLastConnect = rec; 301 } 302 } 303 304 private void logUsbDevice(UsbDescriptorParser descriptorParser) { 305 int vid = 0; 306 int pid = 0; 307 String mfg = "<unknown>"; 308 String product = "<unknown>"; 309 String version = "<unknown>"; 310 String serial = "<unknown>"; 311 312 UsbDeviceDescriptor deviceDescriptor = descriptorParser.getDeviceDescriptor(); 313 if (deviceDescriptor != null) { 314 vid = deviceDescriptor.getVendorID(); 315 pid = deviceDescriptor.getProductID(); 316 mfg = deviceDescriptor.getMfgString(descriptorParser); 317 product = deviceDescriptor.getProductString(descriptorParser); 318 version = deviceDescriptor.getDeviceReleaseString(); 319 serial = deviceDescriptor.getSerialString(descriptorParser); 320 } 321 322 if (vid == LINUX_FOUNDATION_VID) { 323 return; // don't care about OS-constructed virtual USB devices. 324 } 325 boolean hasAudio = descriptorParser.hasAudioInterface(); 326 boolean hasHid = descriptorParser.hasHIDInterface(); 327 boolean hasStorage = descriptorParser.hasStorageInterface(); 328 329 String attachedString = "USB device attached: "; 330 attachedString += String.format("vidpid %04x:%04x", vid, pid); 331 attachedString += String.format(" mfg/product/ver/serial %s/%s/%s/%s", 332 mfg, product, version, serial); 333 attachedString += String.format(" hasAudio/HID/Storage: %b/%b/%b", 334 hasAudio, hasHid, hasStorage); 335 Slog.d(TAG, attachedString); 336 } 337 338 /* Called from JNI in monitorUsbHostBus() to report new USB devices 339 Returns true if successful, i.e. the USB Audio device descriptors are 340 correctly parsed and the unique device is added to the audio device list. 341 */ 342 @SuppressWarnings("unused") 343 private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass, 344 byte[] descriptors) { 345 if (DEBUG) { 346 Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start"); 347 } 348 349 if (isBlackListed(deviceAddress)) { 350 if (DEBUG) { 351 Slog.d(TAG, "device address is black listed"); 352 } 353 return false; 354 } 355 UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors); 356 logUsbDevice(parser); 357 358 if (isBlackListed(deviceClass, deviceSubclass)) { 359 if (DEBUG) { 360 Slog.d(TAG, "device class is black listed"); 361 } 362 return false; 363 } 364 365 synchronized (mLock) { 366 if (mDevices.get(deviceAddress) != null) { 367 Slog.w(TAG, "device already on mDevices list: " + deviceAddress); 368 //TODO If this is the same peripheral as is being connected, replace 369 // it with the new connection. 370 return false; 371 } 372 373 UsbDevice newDevice = parser.toAndroidUsbDevice(); 374 if (newDevice == null) { 375 Slog.e(TAG, "Couldn't create UsbDevice object."); 376 // Tracking 377 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE, 378 parser.getRawDescriptors()); 379 } else { 380 mDevices.put(deviceAddress, newDevice); 381 Slog.d(TAG, "Added device " + newDevice); 382 383 // It is fine to call this only for the current user as all broadcasts are 384 // sent to all profiles of the user and the dialogs should only show once. 385 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); 386 if (usbDeviceConnectionHandler == null) { 387 getCurrentUserSettings().deviceAttached(newDevice); 388 } else { 389 getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice, 390 usbDeviceConnectionHandler); 391 } 392 393 mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser); 394 395 // Tracking 396 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, 397 parser.getRawDescriptors()); 398 } 399 } 400 401 if (DEBUG) { 402 Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end"); 403 } 404 405 return true; 406 } 407 408 /* Called from JNI in monitorUsbHostBus to report USB device removal */ 409 @SuppressWarnings("unused") 410 private void usbDeviceRemoved(String deviceAddress) { 411 synchronized (mLock) { 412 UsbDevice device = mDevices.remove(deviceAddress); 413 if (device != null) { 414 Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName()); 415 mUsbAlsaManager.usbDeviceRemoved(deviceAddress/*device*/); 416 mSettingsManager.usbDeviceRemoved(device); 417 getCurrentUserSettings().usbDeviceRemoved(device); 418 419 // Tracking 420 addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null); 421 } else { 422 Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone"); 423 } 424 } 425 } 426 427 public void systemReady() { 428 synchronized (mLock) { 429 // Create a thread to call into native code to wait for USB host events. 430 // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. 431 Runnable runnable = this::monitorUsbHostBus; 432 new Thread(null, runnable, "UsbService host thread").start(); 433 } 434 } 435 436 /* Returns a list of all currently attached USB devices */ 437 public void getDeviceList(Bundle devices) { 438 synchronized (mLock) { 439 for (String name : mDevices.keySet()) { 440 devices.putParcelable(name, mDevices.get(name)); 441 } 442 } 443 } 444 445 /* Opens the specified USB device */ 446 public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings, 447 String packageName, int uid) { 448 synchronized (mLock) { 449 if (isBlackListed(deviceAddress)) { 450 throw new SecurityException("USB device is on a restricted bus"); 451 } 452 UsbDevice device = mDevices.get(deviceAddress); 453 if (device == null) { 454 // if it is not in mDevices, it either does not exist or is blacklisted 455 throw new IllegalArgumentException( 456 "device " + deviceAddress + " does not exist or is restricted"); 457 } 458 459 settings.checkPermission(device, packageName, uid); 460 return nativeOpenDevice(deviceAddress); 461 } 462 } 463 464 /** 465 * Dump out various information about the state of USB device connections. 466 */ 467 public void dump(DualDumpOutputStream dump, String idName, long id) { 468 long token = dump.start(idName, id); 469 470 synchronized (mHandlerLock) { 471 if (mUsbDeviceConnectionHandler != null) { 472 writeComponentName(dump, "default_usb_host_connection_handler", 473 UsbHostManagerProto.DEFAULT_USB_HOST_CONNECTION_HANDLER, 474 mUsbDeviceConnectionHandler); 475 } 476 } 477 synchronized (mLock) { 478 for (String name : mDevices.keySet()) { 479 writeDevice(dump, "devices", UsbHostManagerProto.DEVICES, mDevices.get(name)); 480 } 481 482 dump.write("num_connects", UsbHostManagerProto.NUM_CONNECTS, mNumConnects); 483 484 for (ConnectionRecord rec : mConnections) { 485 rec.dump(dump, "connections", UsbHostManagerProto.CONNECTIONS); 486 } 487 } 488 489 dump.end(token); 490 } 491 492 /** 493 * Dump various descriptor data. 494 */ 495 public void dumpDescriptors(IndentingPrintWriter pw, String[] args) { 496 if (mLastConnect != null) { 497 pw.println("Last Connected USB Device:"); 498 if (args.length <= 1 || args[1].equals("-dump-short")) { 499 mLastConnect.dumpShort(pw); 500 } else if (args[1].equals("-dump-tree")) { 501 mLastConnect.dumpTree(pw); 502 } else if (args[1].equals("-dump-list")) { 503 mLastConnect.dumpList(pw); 504 } else if (args[1].equals("-dump-raw")) { 505 mLastConnect.dumpRaw(pw); 506 } 507 } else { 508 pw.println("No USB Devices have been connected."); 509 } 510 } 511 512 private native void monitorUsbHostBus(); 513 private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress); 514 } 515