Home | History | Annotate | Download | only in usb
      1 /*
      2  * Copyright (C) 2014 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.content.Context;
     20 import android.content.pm.PackageManager;
     21 import android.content.res.Resources;
     22 import android.hardware.usb.UsbConstants;
     23 import android.hardware.usb.UsbDevice;
     24 import android.hardware.usb.UsbInterface;
     25 import android.media.AudioSystem;
     26 import android.media.IAudioService;
     27 import android.media.midi.MidiDeviceInfo;
     28 import android.os.FileObserver;
     29 import android.os.Bundle;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.os.SystemClock;
     33 import android.provider.Settings;
     34 import android.util.Slog;
     35 
     36 import com.android.internal.alsa.AlsaCardsParser;
     37 import com.android.internal.alsa.AlsaDevicesParser;
     38 import com.android.internal.util.IndentingPrintWriter;
     39 import com.android.server.audio.AudioService;
     40 
     41 import libcore.io.IoUtils;
     42 
     43 import java.io.File;
     44 import java.io.FileDescriptor;
     45 import java.io.PrintWriter;
     46 import java.util.HashMap;
     47 import java.util.ArrayList;
     48 
     49 /**
     50  * UsbAlsaManager manages USB audio and MIDI devices.
     51  */
     52 public final class UsbAlsaManager {
     53     private static final String TAG = UsbAlsaManager.class.getSimpleName();
     54     private static final boolean DEBUG = false;
     55 
     56     private static final String ALSA_DIRECTORY = "/dev/snd/";
     57 
     58     private final Context mContext;
     59     private IAudioService mAudioService;
     60     private final boolean mHasMidiFeature;
     61 
     62     private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
     63     private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
     64 
     65     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
     66     // ALSA device when we are notified that its associated USB device has been removed.
     67 
     68     private final HashMap<UsbDevice,UsbAudioDevice>
     69         mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
     70 
     71     private final HashMap<UsbDevice,UsbMidiDevice>
     72         mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
     73 
     74     private final HashMap<String,AlsaDevice>
     75         mAlsaDevices = new HashMap<String,AlsaDevice>();
     76 
     77     private UsbAudioDevice mAccessoryAudioDevice = null;
     78 
     79     // UsbMidiDevice for USB peripheral mode (gadget) device
     80     private UsbMidiDevice mPeripheralMidiDevice = null;
     81 
     82     private final class AlsaDevice {
     83         public static final int TYPE_UNKNOWN = 0;
     84         public static final int TYPE_PLAYBACK = 1;
     85         public static final int TYPE_CAPTURE = 2;
     86         public static final int TYPE_MIDI = 3;
     87 
     88         public int mCard;
     89         public int mDevice;
     90         public int mType;
     91 
     92         public AlsaDevice(int type, int card, int device) {
     93             mType = type;
     94             mCard = card;
     95             mDevice = device;
     96         }
     97 
     98         public boolean equals(Object obj) {
     99             if (! (obj instanceof AlsaDevice)) {
    100                 return false;
    101             }
    102             AlsaDevice other = (AlsaDevice)obj;
    103             return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice);
    104         }
    105 
    106         public String toString() {
    107             StringBuilder sb = new StringBuilder();
    108             sb.append("AlsaDevice: [card: " + mCard);
    109             sb.append(", device: " + mDevice);
    110             sb.append(", type: " + mType);
    111             sb.append("]");
    112             return sb.toString();
    113         }
    114     }
    115 
    116     private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY,
    117             FileObserver.CREATE | FileObserver.DELETE) {
    118         public void onEvent(int event, String path) {
    119             switch (event) {
    120                 case FileObserver.CREATE:
    121                     alsaFileAdded(path);
    122                     break;
    123                 case FileObserver.DELETE:
    124                     alsaFileRemoved(path);
    125                     break;
    126             }
    127         }
    128     };
    129 
    130     /* package */ UsbAlsaManager(Context context) {
    131         mContext = context;
    132         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
    133 
    134         // initial scan
    135         mCardsParser.scan();
    136     }
    137 
    138     public void systemReady() {
    139         mAudioService = IAudioService.Stub.asInterface(
    140                         ServiceManager.getService(Context.AUDIO_SERVICE));
    141 
    142         mAlsaObserver.startWatching();
    143 
    144         // add existing alsa devices
    145         File[] files = new File(ALSA_DIRECTORY).listFiles();
    146         if (files != null) {
    147             for (int i = 0; i < files.length; i++) {
    148                 alsaFileAdded(files[i].getName());
    149             }
    150         }
    151     }
    152 
    153     // Notifies AudioService when a device is added or removed
    154     // audioDevice - the AudioDevice that was added or removed
    155     // enabled - if true, we're connecting a device (it's arrived), else disconnecting
    156     private void notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled) {
    157         if (DEBUG) {
    158             Slog.d(TAG, "notifyDeviceState " + enabled + " " + audioDevice);
    159         }
    160 
    161         if (mAudioService == null) {
    162             Slog.e(TAG, "no AudioService");
    163             return;
    164         }
    165 
    166         // FIXME Does not yet handle the case where the setting is changed
    167         // after device connection.  Ideally we should handle the settings change
    168         // in SettingsObserver. Here we should log that a USB device is connected
    169         // and disconnected with its address (card , device) and force the
    170         // connection or disconnection when the setting changes.
    171         int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(),
    172                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
    173         if (isDisabled != 0) {
    174             return;
    175         }
    176 
    177         int state = (enabled ? 1 : 0);
    178         int alsaCard = audioDevice.mCard;
    179         int alsaDevice = audioDevice.mDevice;
    180         if (alsaCard < 0 || alsaDevice < 0) {
    181             Slog.e(TAG, "Invalid alsa card or device alsaCard: " + alsaCard +
    182                         " alsaDevice: " + alsaDevice);
    183             return;
    184         }
    185 
    186         String address = AudioService.makeAlsaAddressString(alsaCard, alsaDevice);
    187         try {
    188             // Playback Device
    189             if (audioDevice.mHasPlayback) {
    190                 int device = (audioDevice == mAccessoryAudioDevice ?
    191                         AudioSystem.DEVICE_OUT_USB_ACCESSORY :
    192                         AudioSystem.DEVICE_OUT_USB_DEVICE);
    193                 if (DEBUG) {
    194                     Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
    195                             " addr:" + address + " name:" + audioDevice.mDeviceName);
    196                 }
    197                 mAudioService.setWiredDeviceConnectionState(
    198                         device, state, address, audioDevice.mDeviceName, TAG);
    199             }
    200 
    201             // Capture Device
    202             if (audioDevice.mHasCapture) {
    203                int device = (audioDevice == mAccessoryAudioDevice ?
    204                         AudioSystem.DEVICE_IN_USB_ACCESSORY :
    205                         AudioSystem.DEVICE_IN_USB_DEVICE);
    206                 mAudioService.setWiredDeviceConnectionState(
    207                         device, state, address, audioDevice.mDeviceName, TAG);
    208             }
    209         } catch (RemoteException e) {
    210             Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
    211         }
    212     }
    213 
    214     private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
    215         AlsaDevice testDevice = new AlsaDevice(type, card, device);
    216 
    217         // This value was empirically determined.
    218         final int kWaitTime = 2500; // ms
    219 
    220         synchronized(mAlsaDevices) {
    221             long timeout = SystemClock.elapsedRealtime() + kWaitTime;
    222             do {
    223                 if (mAlsaDevices.values().contains(testDevice)) {
    224                     return testDevice;
    225                 }
    226                 long waitTime = timeout - SystemClock.elapsedRealtime();
    227                 if (waitTime > 0) {
    228                     try {
    229                         mAlsaDevices.wait(waitTime);
    230                     } catch (InterruptedException e) {
    231                         Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
    232                     }
    233                 }
    234             } while (timeout > SystemClock.elapsedRealtime());
    235         }
    236 
    237         Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
    238         return null;
    239     }
    240 
    241     private void alsaFileAdded(String name) {
    242         int type = AlsaDevice.TYPE_UNKNOWN;
    243         int card = -1, device = -1;
    244 
    245         if (name.startsWith("pcmC")) {
    246             if (name.endsWith("p")) {
    247                 type = AlsaDevice.TYPE_PLAYBACK;
    248             } else if (name.endsWith("c")) {
    249                 type = AlsaDevice.TYPE_CAPTURE;
    250             }
    251         } else if (name.startsWith("midiC")) {
    252             type = AlsaDevice.TYPE_MIDI;
    253         }
    254 
    255         if (type != AlsaDevice.TYPE_UNKNOWN) {
    256             try {
    257                 int c_index = name.indexOf('C');
    258                 int d_index = name.indexOf('D');
    259                 int end = name.length();
    260                 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
    261                     // skip trailing 'p' or 'c'
    262                     end--;
    263                 }
    264                 card = Integer.parseInt(name.substring(c_index + 1, d_index));
    265                 device = Integer.parseInt(name.substring(d_index + 1, end));
    266             } catch (Exception e) {
    267                 Slog.e(TAG, "Could not parse ALSA file name " + name, e);
    268                 return;
    269             }
    270             synchronized(mAlsaDevices) {
    271                 if (mAlsaDevices.get(name) == null) {
    272                     AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
    273                     Slog.d(TAG, "Adding ALSA device " + alsaDevice);
    274                     mAlsaDevices.put(name, alsaDevice);
    275                     mAlsaDevices.notifyAll();
    276                 }
    277             }
    278         }
    279     }
    280 
    281     private void alsaFileRemoved(String path) {
    282         synchronized(mAlsaDevices) {
    283             AlsaDevice device = mAlsaDevices.remove(path);
    284             if (device != null) {
    285                 Slog.d(TAG, "ALSA device removed: " + device);
    286             }
    287         }
    288     }
    289 
    290     /*
    291      * Select the default device of the specified card.
    292      */
    293     /* package */ UsbAudioDevice selectAudioCard(int card) {
    294         if (DEBUG) {
    295             Slog.d(TAG, "selectAudioCard() card:" + card);
    296         }
    297         if (!mCardsParser.isCardUsb(card)) {
    298             // Don't. AudioPolicyManager has logic for falling back to internal devices.
    299             return null;
    300         }
    301 
    302         mDevicesParser.scan();
    303         int device = mDevicesParser.getDefaultDeviceNum(card);
    304 
    305         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
    306         boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
    307         int deviceClass =
    308             (mCardsParser.isCardUsb(card)
    309                 ? UsbAudioDevice.kAudioDeviceClass_External
    310                 : UsbAudioDevice.kAudioDeviceClass_Internal) |
    311             UsbAudioDevice.kAudioDeviceMeta_Alsa;
    312 
    313         // Playback device file needed/present?
    314         if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
    315             return null;
    316         }
    317 
    318         // Capture device file needed/present?
    319         if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
    320             return null;
    321         }
    322 
    323         if (DEBUG) {
    324             Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
    325         }
    326 
    327         UsbAudioDevice audioDevice =
    328                 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
    329         AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
    330         audioDevice.mDeviceName = cardRecord.mCardName;
    331         audioDevice.mDeviceDescription = cardRecord.mCardDescription;
    332 
    333         notifyDeviceState(audioDevice, true);
    334 
    335         return audioDevice;
    336     }
    337 
    338     /* package */ UsbAudioDevice selectDefaultDevice() {
    339         if (DEBUG) {
    340             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
    341         }
    342         mCardsParser.scan();
    343         return selectAudioCard(mCardsParser.getDefaultCard());
    344     }
    345 
    346     /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
    347        if (DEBUG) {
    348           Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
    349                   "nm:" + usbDevice.getProductName());
    350         }
    351 
    352         // Is there an audio interface in there?
    353         boolean isAudioDevice = false;
    354 
    355         // FIXME - handle multiple configurations?
    356         int interfaceCount = usbDevice.getInterfaceCount();
    357         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
    358                 ntrfaceIndex++) {
    359             UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
    360             if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
    361                 isAudioDevice = true;
    362             }
    363         }
    364         if (!isAudioDevice) {
    365             return;
    366         }
    367 
    368         ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords();
    369         mCardsParser.scan();
    370 
    371         int addedCard = -1;
    372         ArrayList<AlsaCardsParser.AlsaCardRecord>
    373             newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs);
    374         if (newScanRecs.size() > 0) {
    375             // This is where we select the just connected device
    376             // NOTE - to switch to prefering the first-connected device, just always
    377             // take the else clause below.
    378             addedCard = newScanRecs.get(0).mCardNum;
    379         } else {
    380             addedCard = mCardsParser.getDefaultUsbCard();
    381         }
    382 
    383         // If the default isn't a USB device, let the existing "select internal mechanism"
    384         // handle the selection.
    385         if (mCardsParser.isCardUsb(addedCard)) {
    386             UsbAudioDevice audioDevice = selectAudioCard(addedCard);
    387             if (audioDevice != null) {
    388                 mAudioDevices.put(usbDevice, audioDevice);
    389             }
    390 
    391             // look for MIDI devices
    392 
    393             // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
    394             // Uncomment this next line if that behavior changes in the fugure.
    395             // mDevicesParser.scan()
    396 
    397             boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
    398             if (hasMidi && mHasMidiFeature) {
    399                 int device = mDevicesParser.getDefaultDeviceNum(addedCard);
    400                 AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
    401                 if (alsaDevice != null) {
    402                     Bundle properties = new Bundle();
    403                     String manufacturer = usbDevice.getManufacturerName();
    404                     String product = usbDevice.getProductName();
    405                     String version = usbDevice.getVersion();
    406                     String name;
    407                     if (manufacturer == null || manufacturer.isEmpty()) {
    408                         name = product;
    409                     } else if (product == null || product.isEmpty()) {
    410                         name = manufacturer;
    411                     } else {
    412                         name = manufacturer + " " + product;
    413                     }
    414                     properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
    415                     properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
    416                     properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
    417                     properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
    418                     properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
    419                             usbDevice.getSerialNumber());
    420                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard);
    421                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice);
    422                     properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
    423 
    424                     UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
    425                             alsaDevice.mCard, alsaDevice.mDevice);
    426                     if (usbMidiDevice != null) {
    427                         mMidiDevices.put(usbDevice, usbMidiDevice);
    428                     }
    429                 }
    430             }
    431         }
    432     }
    433 
    434     /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
    435         if (DEBUG) {
    436           Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() +
    437                   " " + usbDevice.getProductName());
    438         }
    439 
    440         UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
    441         if (audioDevice != null) {
    442             if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
    443                 notifyDeviceState(audioDevice, false);
    444 
    445                 // if there any external devices left, select one of them
    446                 selectDefaultDevice();
    447             }
    448         }
    449         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
    450         if (usbMidiDevice != null) {
    451             IoUtils.closeQuietly(usbMidiDevice);
    452         }
    453     }
    454 
    455    /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) {
    456        if (DEBUG) {
    457             Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device);
    458         }
    459         if (enabled) {
    460             mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
    461                     UsbAudioDevice.kAudioDeviceClass_External);
    462             notifyDeviceState(mAccessoryAudioDevice, true);
    463         } else if (mAccessoryAudioDevice != null) {
    464             notifyDeviceState(mAccessoryAudioDevice, false);
    465             mAccessoryAudioDevice = null;
    466         }
    467     }
    468 
    469    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
    470         if (!mHasMidiFeature) {
    471             return;
    472         }
    473 
    474         if (enabled && mPeripheralMidiDevice == null) {
    475             Bundle properties = new Bundle();
    476             Resources r = mContext.getResources();
    477             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
    478                     com.android.internal.R.string.usb_midi_peripheral_name));
    479             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
    480                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
    481             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
    482                     com.android.internal.R.string.usb_midi_peripheral_product_name));
    483             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
    484             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
    485             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
    486         } else if (!enabled && mPeripheralMidiDevice != null) {
    487             IoUtils.closeQuietly(mPeripheralMidiDevice);
    488             mPeripheralMidiDevice = null;
    489         }
    490    }
    491 
    492     //
    493     // Devices List
    494     //
    495     public ArrayList<UsbAudioDevice> getConnectedDevices() {
    496         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
    497         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    498             devices.add(entry.getValue());
    499         }
    500         return devices;
    501     }
    502 
    503     //
    504     // Logging
    505     //
    506     public void dump(IndentingPrintWriter pw) {
    507         pw.println("USB Audio Devices:");
    508         for (UsbDevice device : mAudioDevices.keySet()) {
    509             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
    510         }
    511         pw.println("USB MIDI Devices:");
    512         for (UsbDevice device : mMidiDevices.keySet()) {
    513             pw.println("  " + device.getDeviceName() + ": " + mMidiDevices.get(device));
    514         }
    515     }
    516 
    517     public void logDevicesList(String title) {
    518       if (DEBUG) {
    519           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    520               Slog.i(TAG, "UsbDevice-------------------");
    521               Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
    522               Slog.i(TAG, "UsbAudioDevice--------------");
    523               Slog.i(TAG, "" + entry.getValue());
    524           }
    525       }
    526   }
    527 
    528   // This logs a more terse (and more readable) version of the devices list
    529   public void logDevices(String title) {
    530       if (DEBUG) {
    531           Slog.i(TAG, title);
    532           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    533               Slog.i(TAG, entry.getValue().toShortString());
    534           }
    535       }
    536   }
    537 }
    538