blob: bb45ee88c0dcebac904299ffe09fb4d6c9d1ec0e [file] [log] [blame]
/*
* 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));
}
}
}