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 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