Merge "Implement USB Audio across Nexus Devices Fix issues with connecting non-audio USB devices."

This commit is contained in:
Paul McLean
2014-04-11 21:18:51 +00:00
committed by Android (Google) Code Review
3 changed files with 184 additions and 82 deletions

View File

@@ -205,14 +205,49 @@ public class AlsaDevicesParser {
return mHasPlaybackDevices;
}
public boolean hasPlaybackDevices(int card) {
for (int index = 0; index < deviceRecords_.size(); index++) {
AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
return true;
}
}
return false;
}
public boolean hasCaptureDevices() {
return mHasCaptureDevices;
}
public boolean hasCaptureDevices(int card) {
for (int index = 0; index < deviceRecords_.size(); index++) {
AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
return true;
}
}
return false;
}
public boolean hasMIDIDevices() {
return mHasMIDIDevices;
}
public boolean hasMIDIDevices(int card) {
for (int index = 0; index < deviceRecords_.size(); index++) {
AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
return true;
}
}
return false;
}
public void scan() {
deviceRecords_.clear();

View File

@@ -4083,8 +4083,19 @@ public class AudioService extends IAudioService.Stub {
}
}
}
} else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ||
action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
} else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG)) {
state = intent.getIntExtra("state", 0);
int alsaCard = intent.getIntExtra("card", -1);
int alsaDevice = intent.getIntExtra("device", -1);
String params = (alsaCard == -1 && alsaDevice == -1 ? ""
: "card=" + alsaCard + ";device=" + alsaDevice);
// Playback Device
device = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
setWiredDeviceConnectionState(device, state, params);
} else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
state = intent.getIntExtra("state", 0);
int alsaCard = intent.getIntExtra("card", -1);
@@ -4096,21 +4107,18 @@ public class AudioService extends IAudioService.Stub {
String params = (alsaCard == -1 && alsaDevice == -1 ? ""
: "card=" + alsaCard + ";device=" + alsaDevice);
//TODO(pmclean) - Ignore for now the hasPlayback & hasCapture flags since we
// still need to call setWiredDeviceConnectionState() on disconnect (when we
// don't have card/device files to parse for this info). We will need to store
// that info here when we get smarter about multiple USB card/devices.
// Playback Device
device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
AudioSystem.DEVICE_OUT_USB_ACCESSORY : AudioSystem.DEVICE_OUT_USB_DEVICE;
setWiredDeviceConnectionState(device, state, params);
if (hasPlayback) {
device = AudioSystem.DEVICE_OUT_USB_DEVICE;
setWiredDeviceConnectionState(device, state, params);
}
// Capture Device
device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
AudioSystem.DEVICE_IN_USB_ACCESSORY : AudioSystem.DEVICE_IN_USB_DEVICE;
setWiredDeviceConnectionState(device, state, params);
}
else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
if (hasCapture) {
device = AudioSystem.DEVICE_IN_USB_DEVICE;
setWiredDeviceConnectionState(device, state, params);
}
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
synchronized (mScoClients) {

View File

@@ -29,6 +29,8 @@ import android.os.Parcelable;
import android.os.UserHandle;
import android.util.Slog;
import com.android.alsascan.AlsaCardsParser;
import com.android.alsascan.AlsaDevicesParser;
import com.android.internal.annotations.GuardedBy;
import java.io.File;
@@ -37,7 +39,6 @@ import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
/**
* UsbHostManager manages USB state in host mode.
@@ -62,6 +63,15 @@ public class UsbHostManager {
private ArrayList<UsbInterface> mNewInterfaces;
private ArrayList<UsbEndpoint> mNewEndpoints;
// Attributes of any connected USB audio device.
//TODO(pmclean) When we extend to multiple, USB Audio devices, we will need to get
// more clever about this.
private int mConnectedUsbCard = -1;
private int mConnectedUsbDeviceNum = -1;
private boolean mConnectedHasPlayback = false;
private boolean mConnectedHasCapture = false;
private boolean mConnectedHasMIDI = false;
@GuardedBy("mLock")
private UsbSettingsManager mCurrentSettings;
@@ -112,23 +122,41 @@ public class UsbHostManager {
// device - the ALSA device number of the physical interface
// enabled - if true, we're connecting a device (it's arrived), else disconnecting
private void sendDeviceNotification(int card, int device, boolean enabled,
boolean hasPlayback, boolean hasCapture, boolean hasMIDI) {
// send a sticky broadcast containing current USB state
Intent intent = new Intent(Intent.ACTION_USB_AUDIO_DEVICE_PLUG);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra("state", enabled ? 1 : 0);
intent.putExtra("card", card);
intent.putExtra("device", device);
intent.putExtra("hasPlayback", hasPlayback);
intent.putExtra("hasCapture", hasCapture);
intent.putExtra("hasMIDI", hasMIDI);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
boolean hasPlayback, boolean hasCapture, boolean hasMIDI) {
// send a sticky broadcast containing current USB state
Intent intent = new Intent(Intent.ACTION_USB_AUDIO_DEVICE_PLUG);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra("state", enabled ? 1 : 0);
intent.putExtra("card", card);
intent.putExtra("device", device);
intent.putExtra("hasPlayback", hasPlayback);
intent.putExtra("hasCapture", hasCapture);
intent.putExtra("hasMIDI", hasMIDI);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
static boolean isBuiltInUsbDevice(String deviceName) {
// This may be too broad an assumption
return deviceName.equals("/dev/bus/usb/001/001");
private boolean waitForAlsaFile(int card, int device, boolean capture) {
// These values were empirically determined.
final int kNumRetries = 5;
final int kSleepTime = 500; // ms
String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
File alsaDevFile = new File(alsaDevPath);
boolean exists = false;
for (int retry = 0; !exists && retry < kNumRetries; retry++) {
exists = alsaDevFile.exists();
if (!exists) {
try {
Thread.sleep(kSleepTime);
} catch (IllegalThreadStateException ex) {
Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
} catch (java.lang.InterruptedException ex) {
Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
}
}
}
return exists;
}
/* Called from JNI in monitorUsbHostBus() to report new USB devices
@@ -140,59 +168,25 @@ public class UsbHostManager {
int deviceClass, int deviceSubclass, int deviceProtocol,
String manufacturerName, String productName, String serialNumber) {
if (DEBUG_AUDIO) {
Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
// Audio Class Codes:
// Audio: 0x01
// Audio Subclass Codes:
// undefined: 0x00
// audio control: 0x01
// audio streaming: 0x02
// midi streaming: 0x03
if (DEBUG_AUDIO) {
Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
// Audio Class Codes:
// Audio: 0x01
// Audio Subclass Codes:
// undefined: 0x00
// audio control: 0x01
// audio streaming: 0x02
// midi streaming: 0x03
// some useful debugging info
Slog.d(TAG, "usb:UsbHostManager.usbDeviceAdded()");
Slog.d(TAG, "usb: nm:" + deviceName +
" vnd:" + vendorID +
" prd:" + productID +
" cls:" + deviceClass +
" sub:" + deviceSubclass +
" proto:" + deviceProtocol);
}
if (!isBuiltInUsbDevice(deviceName)) {
//TODO(pmclean) we will need this when we need to support USB interfaces
// beyond card1, device0 but turn them off for now
//com.android.alsascan.AlsaCardsParser cardsParser =
// new com.android.alsascan.AlsaCardsParser();
//cardsParser.scan();
//cardsParser.Log();
// But we need to parse the device to determine its capabilities.
com.android.alsascan.AlsaDevicesParser devicesParser =
new com.android.alsascan.AlsaDevicesParser();
devicesParser.scan();
//devicesParser.Log();
boolean hasPlaybackDevices = devicesParser.hasPlaybackDevices();
boolean hasCaptureDevices = devicesParser.hasCaptureDevices();
boolean hasMIDI = devicesParser.hasMIDIDevices();
if (DEBUG_AUDIO) {
Slog.d(TAG, "usb: hasPlayback:" + hasPlaybackDevices
+ " hasCapture:" + hasCaptureDevices);
}
//TODO(pmclean)
// For now just assume that any USB device that is attached is:
// 1. An audio interface and
// 2. is card:1 device:0
int cardNum = 1;
int deviceNum = 0;
sendDeviceNotification(cardNum, deviceNum, true,
hasPlaybackDevices, hasCaptureDevices, hasMIDI);
// some useful debugging info
Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
+ deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
}
// OK this is non-obvious, but true. One can't tell if the device being attached is even
// potentially an audio device without parsing the interface descriptors, so punt on any
// such test until endUsbDeviceAdded() when we have that info.
if (isBlackListed(deviceName) ||
isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
return false;
@@ -217,6 +211,7 @@ public class UsbHostManager {
mNewInterfaces = new ArrayList<UsbInterface>();
mNewEndpoints = new ArrayList<UsbEndpoint>();
}
return true;
}
@@ -270,6 +265,17 @@ public class UsbHostManager {
mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
}
// Is there an audio interface in there?
final int kUsbClassId_Audio = 0x01;
boolean isAudioDevice = false;
for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < mNewInterfaces.size();
ntrfaceIndex++) {
UsbInterface ntrface = mNewInterfaces.get(ntrfaceIndex);
if (ntrface.getInterfaceClass() == kUsbClassId_Audio) {
isAudioDevice = true;
}
}
synchronized (mLock) {
if (mNewDevice != null) {
mNewDevice.setConfigurations(
@@ -285,6 +291,48 @@ public class UsbHostManager {
mNewInterfaces = null;
mNewEndpoints = null;
}
if (!isAudioDevice) {
return; // bail
}
//TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
// present, unlike the waitForAlsaFile() 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.
mConnectedUsbCard = cardsParser.getNumCardRecords() - 1;
mConnectedUsbDeviceNum = 0;
if (!waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) {
return;
}
mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard);
mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard);
mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard);
if (DEBUG_AUDIO) {
Slog.d(TAG,
"usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture);
}
sendDeviceNotification(mConnectedUsbCard,
mConnectedUsbDeviceNum,
true,
mConnectedHasPlayback,
mConnectedHasCapture,
mConnectedHasMIDI);
}
/* Called from JNI in monitorUsbHostBus to report USB device removal */
@@ -293,8 +341,19 @@ public class UsbHostManager {
Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName);
}
// Same assumptions as the fake-out above
sendDeviceNotification(1, 0, false, /*NA*/false, /*NA*/false, /*NA*/false);
if (mConnectedUsbCard != -1 && mConnectedUsbDeviceNum != -1) {
sendDeviceNotification(mConnectedUsbCard,
mConnectedUsbDeviceNum,
false,
mConnectedHasPlayback,
mConnectedHasCapture,
mConnectedHasMIDI);
mConnectedUsbCard = -1;
mConnectedUsbDeviceNum = -1;
mConnectedHasPlayback = false;
mConnectedHasCapture = false;
mConnectedHasMIDI = false;
}
synchronized (mLock) {
UsbDevice device = mDevices.remove(deviceName);