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