Loading core/java/android/alsa/AlsaCardsParser.java +141 −45 Original line number Diff line number Diff line Loading @@ -22,46 +22,58 @@ import java.io.File; 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); } } Loading @@ -73,10 +85,14 @@ public class AlsaCardsParser { } } private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>(); public AlsaCardsParser() {} public void scan() { cardRecords_.clear(); if (DEBUG) { Slog.i(TAG, "AlsaCardsParser.scan()"); } mCardRecords = new ArrayList<AlsaCardRecord>(); final String cardsFilePath = "/proc/asound/cards"; File cardsFile = new File(cardsFilePath); try { Loading @@ -85,9 +101,18 @@ public class AlsaCardsParser { String line = ""; while ((line = bufferedReader.readLine()) != null) { AlsaCardRecord cardRecord = new AlsaCardRecord(); if (DEBUG) { Slog.i(TAG, " " + line); } cardRecord.parse(line, 0); cardRecord.parse(line = bufferedReader.readLine(), 1); cardRecords_.add(cardRecord); line = bufferedReader.readLine(); if (DEBUG) { Slog.i(TAG, " " + line); } cardRecord.parse(line, 1); mCardRecords.add(cardRecord); } reader.close(); } catch (FileNotFoundException e) { Loading @@ -97,20 +122,91 @@ public class AlsaCardsParser { } } public ArrayList<AlsaCardRecord> getScanRecords() { return mCardRecords; } public AlsaCardRecord getCardRecordAt(int index) { return cardRecords_.get(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 cardRecords_.size(); return mCardRecords.size(); } public void Log() { int numCardRecs = getNumCardRecords(); for (int index = 0; index < numCardRecs; ++index) { Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat()); public boolean isCardUsb(int cardNum) { for (AlsaCardRecord rec : mCardRecords) { if (rec.mCardNum == cardNum) { return rec.mIsUsb; } } public AlsaCardsParser() {} 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()); } } } } core/java/android/alsa/AlsaDevicesParser.java +39 −33 Original line number Diff line number Diff line Loading @@ -21,7 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Vector; import java.util.ArrayList; /** * @hide Loading @@ -29,6 +29,7 @@ import java.util.Vector; */ 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; Loading Loading @@ -58,8 +59,7 @@ public class AlsaDevicesParser { int mDeviceType = kDeviceType_Unknown; int mDeviceDir = kDeviceDir_Unknown; public AlsaDeviceRecord() { } public AlsaDeviceRecord() {} public boolean parse(String line) { // "0123456789012345678901234567890" Loading Loading @@ -176,38 +176,27 @@ public class AlsaDevicesParser { } } private Vector<AlsaDeviceRecord> deviceRecords_ = new Vector<AlsaDeviceRecord>(); private boolean isLineDeviceRecord(String line) { return line.charAt(kIndex_CardDeviceField) == '['; } public AlsaDevicesParser() { } private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>(); public int getNumDeviceRecords() { return deviceRecords_.size(); } public AlsaDevicesParser() {} 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()); } // // Access // public int getDefaultDeviceNum(int card) { // TODO - This (obviously) isn't sufficient. Revisit. return 0; } // // 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) { Loading @@ -222,8 +211,7 @@ public class AlsaDevicesParser { } 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) { Loading @@ -238,8 +226,7 @@ public class AlsaDevicesParser { } 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; Loading @@ -248,8 +235,15 @@ public class AlsaDevicesParser { 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); Loading @@ -261,7 +255,7 @@ public class AlsaDevicesParser { if (isLineDeviceRecord(line)) { AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord(); deviceRecord.parse(line); deviceRecords_.add(deviceRecord); mDeviceRecords.add(deviceRecord); } } reader.close(); Loading @@ -271,5 +265,17 @@ public class AlsaDevicesParser { 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 core/java/android/hardware/usb/UsbDevice.java +1 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.os.Parcelable; 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; Loading media/java/android/media/AudioService.java +6 −2 Original line number Diff line number Diff line Loading @@ -4979,9 +4979,13 @@ public class AudioService extends IAudioService.Stub { 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) { Loading services/usb/java/com/android/server/usb/UsbAlsaManager.java +159 −81 Original line number Diff line number Diff line Loading @@ -37,46 +37,33 @@ import java.io.File; 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; Loading Loading @@ -112,12 +99,6 @@ public class UsbAlsaManager { } } 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) { Loading @@ -134,6 +115,9 @@ public class UsbAlsaManager { /* package */ UsbAlsaManager(Context context) { mContext = context; // initial scan mCardsParser.scan(); } public void systemReady() { Loading @@ -149,9 +133,15 @@ public class UsbAlsaManager { } // 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); Loading @@ -162,6 +152,7 @@ public class UsbAlsaManager { 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); } Loading Loading @@ -241,6 +232,81 @@ public class UsbAlsaManager { } } /* * 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); Loading @@ -263,56 +329,28 @@ public class UsbAlsaManager { 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(); // 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; } // Capture device file needed/present? if (hasCapture && waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null) { return; } ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords(); mCardsParser.scan(); // MIDI device file needed/present? if (hasMidi) { midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI); 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(); } if (DEBUG) { Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture + " hasMidi:" + hasMidi); // 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); } 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); Loading @@ -327,7 +365,7 @@ public class UsbAlsaManager { 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); Loading @@ -340,12 +378,52 @@ public class UsbAlsaManager { } } } 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()); } } } } Loading
core/java/android/alsa/AlsaCardsParser.java +141 −45 Original line number Diff line number Diff line Loading @@ -22,46 +22,58 @@ import java.io.File; 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); } } Loading @@ -73,10 +85,14 @@ public class AlsaCardsParser { } } private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>(); public AlsaCardsParser() {} public void scan() { cardRecords_.clear(); if (DEBUG) { Slog.i(TAG, "AlsaCardsParser.scan()"); } mCardRecords = new ArrayList<AlsaCardRecord>(); final String cardsFilePath = "/proc/asound/cards"; File cardsFile = new File(cardsFilePath); try { Loading @@ -85,9 +101,18 @@ public class AlsaCardsParser { String line = ""; while ((line = bufferedReader.readLine()) != null) { AlsaCardRecord cardRecord = new AlsaCardRecord(); if (DEBUG) { Slog.i(TAG, " " + line); } cardRecord.parse(line, 0); cardRecord.parse(line = bufferedReader.readLine(), 1); cardRecords_.add(cardRecord); line = bufferedReader.readLine(); if (DEBUG) { Slog.i(TAG, " " + line); } cardRecord.parse(line, 1); mCardRecords.add(cardRecord); } reader.close(); } catch (FileNotFoundException e) { Loading @@ -97,20 +122,91 @@ public class AlsaCardsParser { } } public ArrayList<AlsaCardRecord> getScanRecords() { return mCardRecords; } public AlsaCardRecord getCardRecordAt(int index) { return cardRecords_.get(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 cardRecords_.size(); return mCardRecords.size(); } public void Log() { int numCardRecs = getNumCardRecords(); for (int index = 0; index < numCardRecs; ++index) { Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat()); public boolean isCardUsb(int cardNum) { for (AlsaCardRecord rec : mCardRecords) { if (rec.mCardNum == cardNum) { return rec.mIsUsb; } } public AlsaCardsParser() {} 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()); } } } }
core/java/android/alsa/AlsaDevicesParser.java +39 −33 Original line number Diff line number Diff line Loading @@ -21,7 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Vector; import java.util.ArrayList; /** * @hide Loading @@ -29,6 +29,7 @@ import java.util.Vector; */ 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; Loading Loading @@ -58,8 +59,7 @@ public class AlsaDevicesParser { int mDeviceType = kDeviceType_Unknown; int mDeviceDir = kDeviceDir_Unknown; public AlsaDeviceRecord() { } public AlsaDeviceRecord() {} public boolean parse(String line) { // "0123456789012345678901234567890" Loading Loading @@ -176,38 +176,27 @@ public class AlsaDevicesParser { } } private Vector<AlsaDeviceRecord> deviceRecords_ = new Vector<AlsaDeviceRecord>(); private boolean isLineDeviceRecord(String line) { return line.charAt(kIndex_CardDeviceField) == '['; } public AlsaDevicesParser() { } private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>(); public int getNumDeviceRecords() { return deviceRecords_.size(); } public AlsaDevicesParser() {} 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()); } // // Access // public int getDefaultDeviceNum(int card) { // TODO - This (obviously) isn't sufficient. Revisit. return 0; } // // 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) { Loading @@ -222,8 +211,7 @@ public class AlsaDevicesParser { } 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) { Loading @@ -238,8 +226,7 @@ public class AlsaDevicesParser { } 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; Loading @@ -248,8 +235,15 @@ public class AlsaDevicesParser { 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); Loading @@ -261,7 +255,7 @@ public class AlsaDevicesParser { if (isLineDeviceRecord(line)) { AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord(); deviceRecord.parse(line); deviceRecords_.add(deviceRecord); mDeviceRecords.add(deviceRecord); } } reader.close(); Loading @@ -271,5 +265,17 @@ public class AlsaDevicesParser { 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
core/java/android/hardware/usb/UsbDevice.java +1 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.os.Parcelable; 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; Loading
media/java/android/media/AudioService.java +6 −2 Original line number Diff line number Diff line Loading @@ -4979,9 +4979,13 @@ public class AudioService extends IAudioService.Stub { 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) { Loading
services/usb/java/com/android/server/usb/UsbAlsaManager.java +159 −81 Original line number Diff line number Diff line Loading @@ -37,46 +37,33 @@ import java.io.File; 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; Loading Loading @@ -112,12 +99,6 @@ public class UsbAlsaManager { } } 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) { Loading @@ -134,6 +115,9 @@ public class UsbAlsaManager { /* package */ UsbAlsaManager(Context context) { mContext = context; // initial scan mCardsParser.scan(); } public void systemReady() { Loading @@ -149,9 +133,15 @@ public class UsbAlsaManager { } // 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); Loading @@ -162,6 +152,7 @@ public class UsbAlsaManager { 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); } Loading Loading @@ -241,6 +232,81 @@ public class UsbAlsaManager { } } /* * 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); Loading @@ -263,56 +329,28 @@ public class UsbAlsaManager { 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(); // 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; } // Capture device file needed/present? if (hasCapture && waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null) { return; } ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords(); mCardsParser.scan(); // MIDI device file needed/present? if (hasMidi) { midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI); 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(); } if (DEBUG) { Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture + " hasMidi:" + hasMidi); // 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); } 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); Loading @@ -327,7 +365,7 @@ public class UsbAlsaManager { 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); Loading @@ -340,12 +378,52 @@ public class UsbAlsaManager { } } } 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()); } } } }