Home | History | Annotate | Download | only in usb
      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