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         if (DEBUG) {
    216             Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")");
    217         }
    218 
    219         AlsaDevice testDevice = new AlsaDevice(type, card, device);
    220 
    221         // This value was empirically determined.
    222         final int kWaitTime = 2500; // ms
    223 
    224         synchronized(mAlsaDevices) {
    225             long timeout = SystemClock.elapsedRealtime() + kWaitTime;
    226             do {
    227                 if (mAlsaDevices.values().contains(testDevice)) {
    228                     return testDevice;
    229                 }
    230                 long waitTime = timeout - SystemClock.elapsedRealtime();
    231                 if (waitTime > 0) {
    232                     try {
    233                         mAlsaDevices.wait(waitTime);
    234                     } catch (InterruptedException e) {
    235                         Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
    236                     }
    237                 }
    238             } while (timeout > SystemClock.elapsedRealtime());
    239         }
    240 
    241         Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
    242         return null;
    243     }
    244 
    245     private void alsaFileAdded(String name) {
    246         int type = AlsaDevice.TYPE_UNKNOWN;
    247         int card = -1, device = -1;
    248 
    249         if (name.startsWith("pcmC")) {
    250             if (name.endsWith("p")) {
    251                 type = AlsaDevice.TYPE_PLAYBACK;
    252             } else if (name.endsWith("c")) {
    253                 type = AlsaDevice.TYPE_CAPTURE;
    254             }
    255         } else if (name.startsWith("midiC")) {
    256             type = AlsaDevice.TYPE_MIDI;
    257         }
    258 
    259         if (type != AlsaDevice.TYPE_UNKNOWN) {
    260             try {
    261                 int c_index = name.indexOf('C');
    262                 int d_index = name.indexOf('D');
    263                 int end = name.length();
    264                 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
    265                     // skip trailing 'p' or 'c'
    266                     end--;
    267                 }
    268                 card = Integer.parseInt(name.substring(c_index + 1, d_index));
    269                 device = Integer.parseInt(name.substring(d_index + 1, end));
    270             } catch (Exception e) {
    271                 Slog.e(TAG, "Could not parse ALSA file name " + name, e);
    272                 return;
    273             }
    274             synchronized(mAlsaDevices) {
    275                 if (mAlsaDevices.get(name) == null) {
    276                     AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
    277                     Slog.d(TAG, "Adding ALSA device " + alsaDevice);
    278                     mAlsaDevices.put(name, alsaDevice);
    279                     mAlsaDevices.notifyAll();
    280                 }
    281             }
    282         }
    283     }
    284 
    285     private void alsaFileRemoved(String path) {
    286         synchronized(mAlsaDevices) {
    287             AlsaDevice device = mAlsaDevices.remove(path);
    288             if (device != null) {
    289                 Slog.d(TAG, "ALSA device removed: " + device);
    290             }
    291         }
    292     }
    293 
    294     /*
    295      * Select the default device of the specified card.
    296      */
    297     /* package */ UsbAudioDevice selectAudioCard(int card) {
    298         if (DEBUG) {
    299             Slog.d(TAG, "selectAudioCard() card:" + card
    300                     + " isCardUsb(): " + mCardsParser.isCardUsb(card));
    301         }
    302         if (!mCardsParser.isCardUsb(card)) {
    303             // Don't. AudioPolicyManager has logic for falling back to internal devices.
    304             return null;
    305         }
    306 
    307         mDevicesParser.scan();
    308         int device = mDevicesParser.getDefaultDeviceNum(card);
    309 
    310         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
    311         boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
    312         if (DEBUG) {
    313             Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
    314         }
    315 
    316         int deviceClass =
    317             (mCardsParser.isCardUsb(card)
    318                 ? UsbAudioDevice.kAudioDeviceClass_External
    319                 : UsbAudioDevice.kAudioDeviceClass_Internal) |
    320             UsbAudioDevice.kAudioDeviceMeta_Alsa;
    321 
    322         // Playback device file needed/present?
    323         if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
    324             return null;
    325         }
    326 
    327         // Capture device file needed/present?
    328         if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
    329             return null;
    330         }
    331 
    332         UsbAudioDevice audioDevice =
    333                 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
    334         AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
    335         audioDevice.mDeviceName = cardRecord.mCardName;
    336         audioDevice.mDeviceDescription = cardRecord.mCardDescription;
    337 
    338         notifyDeviceState(audioDevice, true);
    339 
    340         return audioDevice;
    341     }
    342 
    343     /* package */ UsbAudioDevice selectDefaultDevice() {
    344         if (DEBUG) {
    345             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
    346         }
    347         return selectAudioCard(mCardsParser.getDefaultCard());
    348     }
    349 
    350     /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
    351        if (DEBUG) {
    352           Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
    353                   " nm:" + usbDevice.getProductName());
    354         }
    355 
    356         // Is there an audio interface in there?
    357         boolean isAudioDevice = false;
    358 
    359         // FIXME - handle multiple configurations?
    360         int interfaceCount = usbDevice.getInterfaceCount();
    361         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
    362                 ntrfaceIndex++) {
    363             UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
    364             if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
    365                 isAudioDevice = true;
    366             }
    367         }
    368 
    369         if (DEBUG) {
    370             Slog.d(TAG, "  isAudioDevice: " + isAudioDevice);
    371         }
    372         if (!isAudioDevice) {
    373             return;
    374         }
    375 
    376         int addedCard = mCardsParser.getDefaultUsbCard();
    377 
    378         // If the default isn't a USB device, let the existing "select internal mechanism"
    379         // handle the selection.
    380         if (DEBUG) {
    381             Slog.d(TAG, "  mCardsParser.isCardUsb(" + addedCard + ") = "
    382                         + mCardsParser.isCardUsb(addedCard));
    383         }
    384         if (mCardsParser.isCardUsb(addedCard)) {
    385             UsbAudioDevice audioDevice = selectAudioCard(addedCard);
    386             if (audioDevice != null) {
    387                 mAudioDevices.put(usbDevice, audioDevice);
    388                 Slog.i(TAG, "USB Audio Device Added: " + 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         if (DEBUG) {
    434             Slog.d(TAG, "deviceAdded() - done");
    435         }
    436     }
    437 
    438     /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
    439         if (DEBUG) {
    440           Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() +
    441                   " " + usbDevice.getProductName());
    442         }
    443 
    444         UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
    445         Slog.i(TAG, "USB Audio Device Removed: " + audioDevice);
    446         if (audioDevice != null) {
    447             if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
    448                 notifyDeviceState(audioDevice, false);
    449 
    450                 // if there any external devices left, select one of them
    451                 selectDefaultDevice();
    452             }
    453         }
    454         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
    455         if (usbMidiDevice != null) {
    456             IoUtils.closeQuietly(usbMidiDevice);
    457         }
    458     }
    459 
    460    /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) {
    461        if (DEBUG) {
    462             Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device);
    463         }
    464         if (enabled) {
    465             mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
    466                     UsbAudioDevice.kAudioDeviceClass_External);
    467             notifyDeviceState(mAccessoryAudioDevice, true);
    468         } else if (mAccessoryAudioDevice != null) {
    469             notifyDeviceState(mAccessoryAudioDevice, false);
    470             mAccessoryAudioDevice = null;
    471         }
    472     }
    473 
    474    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
    475         if (!mHasMidiFeature) {
    476             return;
    477         }
    478 
    479         if (enabled && mPeripheralMidiDevice == null) {
    480             Bundle properties = new Bundle();
    481             Resources r = mContext.getResources();
    482             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
    483                     com.android.internal.R.string.usb_midi_peripheral_name));
    484             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
    485                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
    486             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
    487                     com.android.internal.R.string.usb_midi_peripheral_product_name));
    488             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
    489             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
    490             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
    491         } else if (!enabled && mPeripheralMidiDevice != null) {
    492             IoUtils.closeQuietly(mPeripheralMidiDevice);
    493             mPeripheralMidiDevice = null;
    494         }
    495    }
    496 
    497     //
    498     // Devices List
    499     //
    500     public ArrayList<UsbAudioDevice> getConnectedDevices() {
    501         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
    502         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    503             devices.add(entry.getValue());
    504         }
    505         return devices;
    506     }
    507 
    508     //
    509     // Logging
    510     //
    511     public void dump(IndentingPrintWriter pw) {
    512         pw.println("USB Audio Devices:");
    513         for (UsbDevice device : mAudioDevices.keySet()) {
    514             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
    515         }
    516         pw.println("USB MIDI Devices:");
    517         for (UsbDevice device : mMidiDevices.keySet()) {
    518             pw.println("  " + device.getDeviceName() + ": " + mMidiDevices.get(device));
    519         }
    520     }
    521 
    522     public void logDevicesList(String title) {
    523       if (DEBUG) {
    524           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    525               Slog.i(TAG, "UsbDevice-------------------");
    526               Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
    527               Slog.i(TAG, "UsbAudioDevice--------------");
    528               Slog.i(TAG, "" + entry.getValue());
    529           }
    530       }
    531   }
    532 
    533   // This logs a more terse (and more readable) version of the devices list
    534   public void logDevices(String title) {
    535       if (DEBUG) {
    536           Slog.i(TAG, title);
    537           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
    538               Slog.i(TAG, entry.getValue().toShortString());
    539           }
    540       }
    541   }
    542 }
    543