diff options
author | 2014-10-31 13:34:59 -0700 | |
---|---|---|
committer | 2014-10-31 15:28:52 -0700 | |
commit | 3d50308c7fd2a40dfdeeecdacd44b6ae6d28aafb (patch) | |
tree | 9c13a21749ff76da4518292e11925c6c5076615d | |
parent | cdbdbe3a54f46cbe885fb2bb27d5e79297730bf0 (diff) |
Fix USB audio disconnect logic
Previously, disconnecting any USB device would terminate USB audio playback.
Also moved USB audio support to a separate class and did some prep work for
multiple USB audio device support.
Bug: 18203024
Change-Id: I49822c2c47428e658c853b2ec83c7313e626a1cb
-rw-r--r-- | services/usb/java/com/android/server/usb/UsbAudioManager.java | 197 | ||||
-rw-r--r-- | services/usb/java/com/android/server/usb/UsbHostManager.java | 145 |
2 files changed, 206 insertions, 136 deletions
diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java new file mode 100644 index 000000000000..bb45ee88c0dc --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbAudioManager.java @@ -0,0 +1,197 @@ +/* + * 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; + +import android.alsa.AlsaCardsParser; +import android.alsa.AlsaDevicesParser; +import android.content.Context; +import android.content.Intent; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.media.AudioManager; +import android.os.UserHandle; +import android.util.Slog; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * UsbAudioManager manages USB audio devices. + */ +public class UsbAudioManager { + private static final String TAG = UsbAudioManager.class.getSimpleName(); + private static final boolean DEBUG = false; + + private final Context mContext; + + private final class AudioDevice { + public int mCard; + public int mDevice; + public boolean mHasPlayback; + public boolean mHasCapture; + public boolean mHasMIDI; + + public AudioDevice(int card, int device, + boolean hasPlayback, boolean hasCapture, boolean hasMidi) { + mCard = card; + mDevice = device; + mHasPlayback = hasPlayback; + mHasCapture = hasCapture; + mHasMIDI = hasMidi; + } + + 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,AudioDevice> mAudioDevices + = new HashMap<UsbDevice,AudioDevice>(); + + /* package */ UsbAudioManager(Context context) { + mContext = context; + } + + // Broadcasts the arrival/departure of a USB audio interface + // audioDevice - the AudioDevice 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) { + // 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); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", enabled ? 1 : 0); + intent.putExtra("card", audioDevice.mCard); + intent.putExtra("device", audioDevice.mDevice); + intent.putExtra("hasPlayback", audioDevice.mHasPlayback); + intent.putExtra("hasCapture", audioDevice.mHasCapture); + intent.putExtra("hasMIDI", audioDevice.mHasMIDI); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + 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; + } + + /* package */ void deviceAdded(UsbDevice usbDevice) { + // Is there an audio interface in there? + boolean isAudioDevice = false; + + // FIXME - handle multiple configurations? + int interfaceCount = usbDevice.getInterfaceCount(); + for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; + ntrfaceIndex++) { + UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); + if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { + isAudioDevice = true; + } + } + if (!isAudioDevice) { + return; + } + + //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. + 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 && + !waitForAlsaFile(card, device, false)) { + return; + } + + // Capture device file needed/present? + if (hasCapture && + !waitForAlsaFile(card, device, true)) { + return; + } + + if (DEBUG) { + Slog.d(TAG, + "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); + } + + AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi); + mAudioDevices.put(usbDevice, audioDevice); + sendDeviceNotification(audioDevice, true); + } + + /* package */ void deviceRemoved(UsbDevice device) { + if (DEBUG) { + Slog.d(TAG, "deviceRemoved(): " + device); + } + + AudioDevice audioDevice = mAudioDevices.remove(device); + if (audioDevice != null) { + sendDeviceNotification(audioDevice, false); + } + } + + public void dump(FileDescriptor fd, PrintWriter pw) { + pw.println(" USB AudioDevices:"); + for (UsbDevice device : mAudioDevices.keySet()) { + pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 06febb3bd2bf..e769bda1eb6f 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -16,8 +16,6 @@ package com.android.server.usb; -import android.alsa.AlsaCardsParser; -import android.alsa.AlsaDevicesParser; import android.content.Context; import android.content.Intent; import android.hardware.usb.UsbConfiguration; @@ -25,16 +23,13 @@ import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; -import android.media.AudioManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.PrintWriter; @@ -46,11 +41,12 @@ import java.util.HashMap; */ public class UsbHostManager { private static final String TAG = UsbHostManager.class.getSimpleName(); - private static final boolean DEBUG_AUDIO = false; + private static final boolean DEBUG = false; // contains all connected USB devices private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>(); + // USB busses to exclude from USB host support private final String[] mHostBlacklist; @@ -64,14 +60,7 @@ 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; + private UsbAudioManager mUsbAudioManager; @GuardedBy("mLock") private UsbSettingsManager mCurrentSettings; @@ -80,6 +69,7 @@ public class UsbHostManager { mContext = context; mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); + mUsbAudioManager = new UsbAudioManager(context); } public void setCurrentSettings(UsbSettingsManager settings) { @@ -118,48 +108,6 @@ public class UsbHostManager { return false; } - // Broadcasts the arrival/departure of a USB audio interface - // card - the ALSA card number of the physical interface - // 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(AudioManager.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); - } - - 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 Returns true if successful, in which case the JNI code will continue adding configurations, interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors @@ -169,7 +117,7 @@ public class UsbHostManager { int deviceClass, int deviceSubclass, int deviceProtocol, String manufacturerName, String productName, String serialNumber) { - if (DEBUG_AUDIO) { + if (DEBUG) { Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")"); // Audio Class Codes: // Audio: 0x01 @@ -254,7 +202,7 @@ public class UsbHostManager { /* Called from JNI in monitorUsbHostBus() to finish adding a new device */ private void endUsbDeviceAdded() { - if (DEBUG_AUDIO) { + if (DEBUG) { Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()"); } if (mNewInterface != null) { @@ -266,16 +214,6 @@ 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) { @@ -284,6 +222,7 @@ public class UsbHostManager { mDevices.put(mNewDevice.getDeviceName(), mNewDevice); Slog.d(TAG, "Added device " + mNewDevice); getCurrentSettings().deviceAttached(mNewDevice); + mUsbAudioManager.deviceAdded(mNewDevice); } else { Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); } @@ -292,81 +231,14 @@ 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; - - mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard); - mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard); - mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard); - - // Playback device file needed/present? - if (mConnectedHasPlayback && - !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) { - return; - } - - // Capture device file needed/present? - if (mConnectedHasCapture && - !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, true)) { - return; - } - - 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 */ private void usbDeviceRemoved(String deviceName) { - if (DEBUG_AUDIO) { - Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName); - } - - 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); if (device != null) { + mUsbAudioManager.deviceRemoved(device); getCurrentSettings().deviceDetached(device); } } @@ -418,6 +290,7 @@ public class UsbHostManager { pw.println(" " + name + ": " + mDevices.get(name)); } } + mUsbAudioManager.dump(fd, pw); } private native void monitorUsbHostBus(); |