Support for simultaneous USB Audio Devices connect/disconnect

Attach/Detach logic
Decoupling card# and list index
Modern loop constructs
Consistent logging flags
Add class/type flags to device "address" string.
Factored UsbAudioDevice out of UsbAudioManager.

Bug: 18399845
Bug: 18717784

Change-Id: I6f185e1c24091d4c0d21eb7e922a1496748d32c3
diff --git a/core/java/android/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java
index 8b44881..8f9c56c 100644
--- a/core/java/android/alsa/AlsaCardsParser.java
+++ b/core/java/android/alsa/AlsaCardsParser.java
@@ -22,46 +22,58 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.Vector;
+import java.util.ArrayList;
 
 /**
  * @hide Retrieves information from an ALSA "cards" file.
  */
 public class AlsaCardsParser {
     private static final String TAG = "AlsaCardsParser";
+    protected static final boolean DEBUG = true;
 
-    private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]");
+    private static LineTokenizer mTokenizer = new LineTokenizer(" :[]");
+
+    private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
 
     public class AlsaCardRecord {
+        private static final String TAG = "AlsaCardRecord";
+        private static final String kUsbCardKeyStr = "at usb-";
+
         public int mCardNum = -1;
         public String mField1 = "";
         public String mCardName = "";
         public String mCardDescription = "";
+        public boolean mIsUsb = false;
 
         public AlsaCardRecord() {}
 
         public boolean parse(String line, int lineIndex) {
             int tokenIndex = 0;
             int delimIndex = 0;
+
             if (lineIndex == 0) {
                 // line # (skip)
-                tokenIndex = tokenizer_.nextToken(line, tokenIndex);
-                delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+                tokenIndex = mTokenizer.nextToken(line, tokenIndex);
+                delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
+
+                // mCardNum
+                mCardNum = Integer.parseInt(line.substring(tokenIndex, delimIndex));
 
                 // mField1
-                tokenIndex = tokenizer_.nextToken(line, delimIndex);
-                delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+                tokenIndex = mTokenizer.nextToken(line, delimIndex);
+                delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
                 mField1 = line.substring(tokenIndex, delimIndex);
 
                 // mCardName
-                tokenIndex = tokenizer_.nextToken(line, delimIndex);
-                // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+                tokenIndex = mTokenizer.nextToken(line, delimIndex);
                 mCardName = line.substring(tokenIndex);
+
                 // done
               } else if (lineIndex == 1) {
-                  tokenIndex = tokenizer_.nextToken(line, 0);
+                  tokenIndex = mTokenizer.nextToken(line, 0);
                   if (tokenIndex != -1) {
                       mCardDescription = line.substring(tokenIndex);
+                      mIsUsb = mCardDescription.contains(kUsbCardKeyStr);
                   }
             }
 
@@ -73,44 +85,128 @@
         }
     }
 
-    private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>();
+    public AlsaCardsParser() {}
 
     public void scan() {
-          cardRecords_.clear();
-          final String cardsFilePath = "/proc/asound/cards";
-          File cardsFile = new File(cardsFilePath);
-          try {
-              FileReader reader = new FileReader(cardsFile);
-              BufferedReader bufferedReader = new BufferedReader(reader);
-              String line = "";
-              while ((line = bufferedReader.readLine()) != null) {
-                  AlsaCardRecord cardRecord = new AlsaCardRecord();
-                  cardRecord.parse(line, 0);
-                  cardRecord.parse(line = bufferedReader.readLine(), 1);
-                  cardRecords_.add(cardRecord);
-              }
-              reader.close();
-          } catch (FileNotFoundException e) {
-              e.printStackTrace();
-          } catch (IOException e) {
-              e.printStackTrace();
-          }
-      }
+        if (DEBUG) {
+            Slog.i(TAG, "AlsaCardsParser.scan()");
+        }
+        mCardRecords = new ArrayList<AlsaCardRecord>();
 
-      public AlsaCardRecord getCardRecordAt(int index) {
-          return cardRecords_.get(index);
-      }
+        final String cardsFilePath = "/proc/asound/cards";
+        File cardsFile = new File(cardsFilePath);
+        try {
+            FileReader reader = new FileReader(cardsFile);
+            BufferedReader bufferedReader = new BufferedReader(reader);
+            String line = "";
+            while ((line = bufferedReader.readLine()) != null) {
+                AlsaCardRecord cardRecord = new AlsaCardRecord();
+                if (DEBUG) {
+                    Slog.i(TAG, "  " + line);
+                }
+                cardRecord.parse(line, 0);
 
-      public int getNumCardRecords() {
-          return cardRecords_.size();
-      }
+                line = bufferedReader.readLine();
+                if (DEBUG) {
+                    Slog.i(TAG, "  " + line);
+                }
+                cardRecord.parse(line, 1);
 
-    public void Log() {
-      int numCardRecs = getNumCardRecords();
-      for (int index = 0; index < numCardRecs; ++index) {
-          Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat());
-      }
+                mCardRecords.add(cardRecord);
+            }
+            reader.close();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 
-    public AlsaCardsParser() {}
+    public ArrayList<AlsaCardRecord> getScanRecords() {
+        return mCardRecords;
+    }
+
+    public AlsaCardRecord getCardRecordAt(int index) {
+        return mCardRecords.get(index);
+    }
+
+    public AlsaCardRecord getCardRecordFor(int cardNum) {
+        for (AlsaCardRecord rec : mCardRecords) {
+            if (rec.mCardNum == cardNum) {
+                return rec;
+            }
+        }
+
+        return null;
+    }
+
+    public int getNumCardRecords() {
+        return mCardRecords.size();
+    }
+
+    public boolean isCardUsb(int cardNum) {
+        for (AlsaCardRecord rec : mCardRecords) {
+            if (rec.mCardNum == cardNum) {
+                return rec.mIsUsb;
+            }
+        }
+
+        return false;
+    }
+
+    // return -1 if none found
+    public int getDefaultUsbCard() {
+        // Choose the most-recently added EXTERNAL card
+        // or return the first added EXTERNAL card?
+        for (AlsaCardRecord rec : mCardRecords) {
+            if (rec.mIsUsb) {
+                return rec.mCardNum;
+            }
+        }
+
+        return -1;
+    }
+
+    public int getDefaultCard() {
+        // return an external card if possible
+        int card = getDefaultUsbCard();
+
+        if (card < 0 && getNumCardRecords() > 0) {
+            // otherwise return the (internal) card with the highest number
+            card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
+        }
+        return card;
+    }
+
+    static public boolean hasCardNumber(ArrayList<AlsaCardRecord> recs, int cardNum) {
+        for (AlsaCardRecord cardRec : recs) {
+            if (cardRec.mCardNum == cardNum) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ArrayList<AlsaCardRecord> getNewCardRecords(ArrayList<AlsaCardRecord> prevScanRecs) {
+        ArrayList<AlsaCardRecord> newRecs = new ArrayList<AlsaCardRecord>();
+        for (AlsaCardRecord rec : mCardRecords) {
+            // now scan to see if this card number is in the previous scan list
+            if (!hasCardNumber(prevScanRecs, rec.mCardNum)) {
+                newRecs.add(rec);
+            }
+        }
+        return newRecs;
+    }
+
+    //
+    // Logging
+    //
+    public void Log(String heading) {
+        if (DEBUG) {
+            Slog.i(TAG, heading);
+            for (AlsaCardRecord cardRec : mCardRecords) {
+                Slog.i(TAG, cardRec.textFormat());
+            }
+        }
+    }
 }
diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/android/alsa/AlsaDevicesParser.java
index 82cc1ae..3581cb6 100644
--- a/core/java/android/alsa/AlsaDevicesParser.java
+++ b/core/java/android/alsa/AlsaDevicesParser.java
@@ -21,7 +21,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.Vector;
+import java.util.ArrayList;
 
 /**
  * @hide
@@ -29,6 +29,7 @@
  */
 public class AlsaDevicesParser {
     private static final String TAG = "AlsaDevicesParser";
+    protected static final boolean DEBUG = false;
 
     private static final int kIndex_CardDeviceField = 5;
     private static final int kStartIndex_CardNum = 6;
@@ -58,8 +59,7 @@
         int mDeviceType = kDeviceType_Unknown;
         int mDeviceDir = kDeviceDir_Unknown;
 
-        public AlsaDeviceRecord() {
-        }
+        public AlsaDeviceRecord() {}
 
         public boolean parse(String line) {
             // "0123456789012345678901234567890"
@@ -176,38 +176,27 @@
         }
     }
 
-    private Vector<AlsaDeviceRecord>
-            deviceRecords_ = new Vector<AlsaDeviceRecord>();
+    private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>();
 
-    private boolean isLineDeviceRecord(String line) {
-        return line.charAt(kIndex_CardDeviceField) == '[';
+    public AlsaDevicesParser() {}
+
+    //
+    // Access
+    //
+    public int getDefaultDeviceNum(int card) {
+        // TODO - This (obviously) isn't sufficient. Revisit.
+        return 0;
     }
 
-    public AlsaDevicesParser() {
-    }
-
-    public int getNumDeviceRecords() {
-        return deviceRecords_.size();
-    }
-
-    public AlsaDeviceRecord getDeviceRecordAt(int index) {
-        return deviceRecords_.get(index);
-    }
-
-    public void Log() {
-        int numDevRecs = getNumDeviceRecords();
-        for (int index = 0; index < numDevRecs; ++index) {
-            Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat());
-        }
-    }
-
-    public boolean hasPlaybackDevices() {
+    //
+    // Predicates
+    //
+   public boolean hasPlaybackDevices() {
         return mHasPlaybackDevices;
     }
 
     public boolean hasPlaybackDevices(int card) {
-        for (int index = 0; index < deviceRecords_.size(); index++) {
-            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
             if (deviceRecord.mCardNum == card &&
                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
@@ -222,8 +211,7 @@
     }
 
     public boolean hasCaptureDevices(int card) {
-        for (int index = 0; index < deviceRecords_.size(); index++) {
-            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
             if (deviceRecord.mCardNum == card &&
                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
@@ -238,8 +226,7 @@
     }
 
     public boolean hasMIDIDevices(int card) {
-        for (int index = 0; index < deviceRecords_.size(); index++) {
-            AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
             if (deviceRecord.mCardNum == card &&
                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
                 return true;
@@ -248,8 +235,15 @@
         return false;
     }
 
+    //
+    // Process
+    //
+    private boolean isLineDeviceRecord(String line) {
+        return line.charAt(kIndex_CardDeviceField) == '[';
+    }
+
     public void scan() {
-        deviceRecords_.clear();
+        mDeviceRecords.clear();
 
         final String devicesFilePath = "/proc/asound/devices";
         File devicesFile = new File(devicesFilePath);
@@ -261,7 +255,7 @@
                 if (isLineDeviceRecord(line)) {
                     AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
                     deviceRecord.parse(line);
-                    deviceRecords_.add(deviceRecord);
+                    mDeviceRecords.add(deviceRecord);
                 }
             }
             reader.close();
@@ -271,5 +265,17 @@
             e.printStackTrace();
         }
     }
+
+    //
+    // Loging
+    //
+    public void Log(String heading) {
+        if (DEBUG) {
+            Slog.i(TAG, heading);
+            for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
+                Slog.i(TAG, deviceRecord.textFormat());
+            }
+        }
+    }
 } // class AlsaDevicesParser
 
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index d90e06e..1a42319 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -40,6 +40,7 @@
 public class UsbDevice implements Parcelable {
 
     private static final String TAG = "UsbDevice";
+    private static final boolean DEBUG = false;
 
     private final String mName;
     private final String mManufacturerName;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 430ead5..a7965b4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -4979,9 +4979,13 @@
                 boolean hasPlayback = intent.getBooleanExtra("hasPlayback", false);
                 boolean hasCapture = intent.getBooleanExtra("hasCapture", false);
                 boolean hasMIDI = intent.getBooleanExtra("hasMIDI", false);
+                int deviceClass = intent.getIntExtra("class", 0);
 
-                String params = (alsaCard == -1 && alsaDevice == -1 ? ""
-                                    : "card=" + alsaCard + ";device=" + alsaDevice);
+                String params = (alsaCard == -1 && alsaDevice == -1
+                        ? ""
+                        : "card=" + alsaCard +
+                          ";device=" + alsaDevice +
+                          ";class=" + Integer.toHexString(deviceClass));
 
                 // Playback Device
                 if (hasPlayback) {
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 6cc8ff5..32ca723 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -37,46 +37,33 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.ArrayList;
 
 /**
  * UsbAlsaManager manages USB audio and MIDI devices.
  */
 public class UsbAlsaManager {
     private static final String TAG = UsbAlsaManager.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     private static final String ALSA_DIRECTORY = "/dev/snd/";
 
     private final Context mContext;
     private IMidiManager mMidiManager;
 
-    private final class AudioDevice {
-        public int mCard;
-        public int mDevice;
-        public boolean mHasPlayback;
-        public boolean mHasCapture;
-        public boolean mHasMIDI;
+    private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
+    private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
 
-        public AudioDevice(int card, int device,
-                boolean hasPlayback, boolean hasCapture, boolean hasMidi) {
-            mCard = card;
-            mDevice = device;
-            mHasPlayback = hasPlayback;
-            mHasCapture = hasCapture;
-            mHasMIDI = hasMidi;
-        }
+    // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
+    // ALSA device when we are notified that its associated USB device has been removed.
 
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("AudioDevice: [card: " + mCard);
-            sb.append(", device: " + mDevice);
-            sb.append(", hasPlayback: " + mHasPlayback);
-            sb.append(", hasCapture: " + mHasCapture);
-            sb.append(", hasMidi: " + mHasMIDI);
-            sb.append("]");
-            return sb.toString();
-        }
-    }
+    private final HashMap<UsbDevice,UsbAudioDevice>
+        mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
+
+    private final HashMap<String,AlsaDevice>
+        mAlsaDevices = new HashMap<String,AlsaDevice>();
+
+    private UsbAudioDevice mSelectedAudioDevice = null;
 
     private final class AlsaDevice {
         public static final int TYPE_UNKNOWN = 0;
@@ -112,12 +99,6 @@
         }
     }
 
-    private final HashMap<UsbDevice,AudioDevice> mAudioDevices
-            = new HashMap<UsbDevice,AudioDevice>();
-
-    private final HashMap<String,AlsaDevice> mAlsaDevices
-            = new HashMap<String,AlsaDevice>();
-
     private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY,
             FileObserver.CREATE | FileObserver.DELETE) {
         public void onEvent(int event, String path) {
@@ -134,6 +115,9 @@
 
     /* package */ UsbAlsaManager(Context context) {
         mContext = context;
+
+        // initial scan
+        mCardsParser.scan();
     }
 
     public void systemReady() {
@@ -149,9 +133,15 @@
     }
 
     // Broadcasts the arrival/departure of a USB audio interface
-    // audioDevice - the AudioDevice that was added or removed
+    // audioDevice - the UsbAudioDevice that was added or removed
     // enabled - if true, we're connecting a device (it's arrived), else disconnecting
-    private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) {
+    private void sendDeviceNotification(UsbAudioDevice audioDevice, boolean enabled) {
+        if (DEBUG) {
+            Slog.d(TAG, "sendDeviceNotification(enabled:" + enabled +
+                    " c:" + audioDevice.mCard +
+                    " d:" + audioDevice.mDevice + ")");
+        }
+
         // send a sticky broadcast containing current USB state
         Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
@@ -162,6 +152,7 @@
         intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
         intent.putExtra("hasCapture", audioDevice.mHasCapture);
         intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
+        intent.putExtra("class", audioDevice.mDeviceClass);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -241,6 +232,81 @@
         }
     }
 
+    /*
+     * Select the default device of the specified card.
+     */
+    /* package */ boolean selectCard(int card) {
+        if (DEBUG) {
+            Slog.d(TAG, "selectCard() card:" + card);
+        }
+        if (!mCardsParser.isCardUsb(card)) {
+            // Don't. AudioPolicyManager has logic for falling back to internal devices.
+            return false;
+        }
+
+        if (mSelectedAudioDevice != null) {
+            if (mSelectedAudioDevice.mCard == card) {
+                // Nothing to do here.
+                return false;
+            }
+            // "disconnect" the AudioPolicyManager from the previously selected device.
+            sendDeviceNotification(mSelectedAudioDevice, false);
+            mSelectedAudioDevice = null;
+        }
+
+        mDevicesParser.scan();
+        int device = mDevicesParser.getDefaultDeviceNum(card);
+
+        boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
+        boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
+        boolean hasMidi = mDevicesParser.hasMIDIDevices(card);
+        int deviceClass =
+            (mCardsParser.isCardUsb(card)
+                ? UsbAudioDevice.kAudioDeviceClass_External
+                : UsbAudioDevice.kAudioDeviceClass_Internal) |
+            UsbAudioDevice.kAudioDeviceMeta_Alsa;
+
+        // Playback device file needed/present?
+        if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
+            return false;
+        }
+
+        // Capture device file needed/present?
+        if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
+            return false;
+        }
+        //TODO - seems to me that we need to decouple the above tests for audio
+        // from the one below for MIDI.
+
+        // MIDI device file needed/present?
+        AlsaDevice midiDevice = null;
+        if (hasMidi) {
+            midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
+        }
+
+        mSelectedAudioDevice =
+                new UsbAudioDevice(card, device, hasPlayback, hasCapture, hasMidi, deviceClass);
+        mSelectedAudioDevice.mDeviceName = mCardsParser.getCardRecordFor(card).mCardName;
+        mSelectedAudioDevice.mDeviceDescription =
+                mCardsParser.getCardRecordFor(card).mCardDescription;
+
+        sendDeviceNotification(mSelectedAudioDevice, true);
+
+        return true;
+    }
+
+    /* package */ boolean selectDefaultDevice() {
+        if (DEBUG) {
+            Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
+        }
+        mCardsParser.scan();
+        return selectCard(mCardsParser.getDefaultCard());
+    }
+
     /* package */ void deviceAdded(UsbDevice usbDevice) {
        if (DEBUG) {
           Slog.d(TAG, "deviceAdded(): " + usbDevice);
@@ -263,56 +329,28 @@
             return;
         }
 
-        //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
-        // present, unlike the waitForAlsaDevice() which waits on a file in /dev/snd. It is not
-        // clear why this works, or that it can be relied on going forward.  Needs further
-        // research.
-        AlsaCardsParser cardsParser = new AlsaCardsParser();
-        cardsParser.scan();
-        // cardsParser.Log();
+        ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords();
+        mCardsParser.scan();
 
-        // But we need to parse the device to determine its capabilities.
-        AlsaDevicesParser devicesParser = new AlsaDevicesParser();
-        devicesParser.scan();
-        // devicesParser.Log();
-
-        // The protocol for now will be to select the last-connected (highest-numbered)
-        // Alsa Card.
-        int card = cardsParser.getNumCardRecords() - 1;
-        int device = 0;
-
-        boolean hasPlayback = devicesParser.hasPlaybackDevices(card);
-        boolean hasCapture = devicesParser.hasCaptureDevices(card);
-        boolean hasMidi = devicesParser.hasMIDIDevices(card);
-
-        // Playback device file needed/present?
-        if (hasPlayback &&
-            waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null) {
-            return;
+        int addedCard = -1;
+        ArrayList<AlsaCardsParser.AlsaCardRecord>
+            newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs);
+        if (newScanRecs.size() > 0) {
+            // This is where we select the just connected device
+            // NOTE - to switch to prefering the first-connected device, just always
+            // take the else clause below.
+            addedCard = newScanRecs.get(0).mCardNum;
+        } else {
+            addedCard = mCardsParser.getDefaultUsbCard();
         }
 
-        // Capture device file needed/present?
-        if (hasCapture &&
-            waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null) {
-            return;
+        // If the default isn't a USB device, let the existing "select internal mechanism"
+        // handle the selection.
+        if (mCardsParser.isCardUsb(addedCard)) {
+            selectCard(addedCard);
+            mAudioDevices.put(usbDevice, mSelectedAudioDevice);
         }
 
-        // MIDI device file needed/present?
-        if (hasMidi) {
-            midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
-        }
-
-        if (DEBUG) {
-            Slog.d(TAG,
-                    "usb: hasPlayback:" + hasPlayback +
-                    " hasCapture:" + hasCapture +
-                    " hasMidi:" + hasMidi);
-        }
-
-        AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
-        mAudioDevices.put(usbDevice, audioDevice);
-        sendDeviceNotification(audioDevice, true);
-
         if (midiDevice != null && mMidiManager != null) {
             try {
                 mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
@@ -327,7 +365,7 @@
           Slog.d(TAG, "deviceRemoved(): " + device);
         }
 
-        AudioDevice audioDevice = mAudioDevices.remove(device);
+        UsbAudioDevice audioDevice = mAudioDevices.remove(device);
         if (audioDevice != null) {
             if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
                 sendDeviceNotification(audioDevice, false);
@@ -340,12 +378,52 @@
                 }
             }
         }
+
+        mSelectedAudioDevice = null;
+
+        // if there any external devices left, select one of them
+        selectDefaultDevice();
     }
 
+    //
+    // Devices List
+    //
+    public ArrayList<UsbAudioDevice> getConnectedDevices() {
+        ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
+        for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+            devices.add(entry.getValue());
+        }
+        return devices;
+    }
+
+    //
+    // Logging
+    //
     public void dump(FileDescriptor fd, PrintWriter pw) {
         pw.println("  USB AudioDevices:");
         for (UsbDevice device : mAudioDevices.keySet()) {
             pw.println("    " + device.getDeviceName() + ": " + mAudioDevices.get(device));
         }
     }
+
+    public void logDevicesList(String title) {
+      if (DEBUG) {
+          for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+              Slog.i(TAG, "UsbDevice-------------------");
+              Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
+              Slog.i(TAG, "UsbAudioDevice--------------");
+              Slog.i(TAG, "" + entry.getValue());
+          }
+      }
+  }
+
+  // This logs a more terse (and more readable) version of the devices list
+  public void logDevices(String title) {
+      if (DEBUG) {
+          Slog.i(TAG, title);
+          for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+              Slog.i(TAG, entry.getValue().toShortString());
+          }
+      }
+  }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
new file mode 100644
index 0000000..b7b9563
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+public final class UsbAudioDevice {
+    private static final String TAG = "UsbAudioDevice";
+    protected static final boolean DEBUG = false;
+
+    public int mCard;
+    public int mDevice;
+    public boolean mHasPlayback;
+    public boolean mHasCapture;
+    public boolean mHasMIDI;
+
+    // Device "class" flags
+    public static final int kAudioDeviceClassMask = 0x00FFFFFF;
+    public static final int kAudioDeviceClass_Undefined = 0x00000000;
+    public static final int kAudioDeviceClass_Internal = 0x00000001;
+    public static final int kAudioDeviceClass_External = 0x00000002;
+    // Device meta-data flags
+    public static final int kAudioDeviceMetaMask = 0xFF000000;
+    public static final int kAudioDeviceMeta_Alsa = 0x80000000;
+    // This member is a combination of the above bit-flags
+    public int mDeviceClass;
+
+    public String mDeviceName = "";
+    public String mDeviceDescription = "";
+
+    public UsbAudioDevice(int card, int device,
+            boolean hasPlayback, boolean hasCapture, boolean hasMidi, int deviceClass) {
+        mCard = card;
+        mDevice = device;
+        mHasPlayback = hasPlayback;
+        mHasCapture = hasCapture;
+        mHasMIDI = hasMidi;
+        mDeviceClass = deviceClass;
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("UsbAudioDevice: [card: " + mCard);
+        sb.append(", device: " + mDevice);
+        sb.append(", name: " + mDeviceName);
+        sb.append(", description: " + mDeviceDescription);
+        sb.append(", hasPlayback: " + mHasPlayback);
+        sb.append(", hasCapture: " + mHasCapture);
+        sb.append(", hasMidi: " + mHasMIDI);
+        sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]");
+        return sb.toString();
+    }
+
+    public String toShortString() {
+        return "[card:" + mCard + " device:" + mDevice + " " + mDeviceName + "]";
+    }
+}
+