1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions an
14  * limitations under the License.
15  */
16 
17 package com.android.server.usb;
18 
19 import android.alsa.AlsaCardsParser;
20 import android.alsa.AlsaDevicesParser;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.hardware.usb.UsbConstants;
24 import android.hardware.usb.UsbDevice;
25 import android.hardware.usb.UsbInterface;
26 import android.media.AudioManager;
27 import android.os.UserHandle;
28 import android.util.Slog;
29 
30 import java.io.File;
31 import java.io.FileDescriptor;
32 import java.io.PrintWriter;
33 import java.util.HashMap;
34 
35 /**
36  * UsbAudioManager manages USB audio devices.
37  */
38 public class UsbAudioManager {
39     private static final String TAG = UsbAudioManager.class.getSimpleName();
40     private static final boolean DEBUG = false;
41 
42     private final Context mContext;
43 
44     private final class AudioDevice {
45         public int mCard;
46         public int mDevice;
47         public boolean mHasPlayback;
48         public boolean mHasCapture;
49         public boolean mHasMIDI;
50 
AudioDevice(int card, int device, boolean hasPlayback, boolean hasCapture, boolean hasMidi)51         public AudioDevice(int card, int device,
52                 boolean hasPlayback, boolean hasCapture, boolean hasMidi) {
53             mCard = card;
54             mDevice = device;
55             mHasPlayback = hasPlayback;
56             mHasCapture = hasCapture;
57             mHasMIDI = hasMidi;
58         }
59 
toString()60         public String toString() {
61             StringBuilder sb = new StringBuilder();
62             sb.append("AudioDevice: [card: " + mCard);
63             sb.append(", device: " + mDevice);
64             sb.append(", hasPlayback: " + mHasPlayback);
65             sb.append(", hasCapture: " + mHasCapture);
66             sb.append(", hasMidi: " + mHasMIDI);
67             sb.append("]");
68             return sb.toString();
69         }
70     }
71 
72     private final HashMap<UsbDevice,AudioDevice> mAudioDevices
73             = new HashMap<UsbDevice,AudioDevice>();
74 
UsbAudioManager(Context context)75     /* package */ UsbAudioManager(Context context) {
76         mContext = context;
77     }
78 
79     // Broadcasts the arrival/departure of a USB audio interface
80     // audioDevice - the AudioDevice that was added or removed
81     // enabled - if true, we're connecting a device (it's arrived), else disconnecting
sendDeviceNotification(AudioDevice audioDevice, boolean enabled)82     private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) {
83         // send a sticky broadcast containing current USB state
84         Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
85         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
86         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
87         intent.putExtra("state", enabled ? 1 : 0);
88         intent.putExtra("card", audioDevice.mCard);
89         intent.putExtra("device", audioDevice.mDevice);
90         intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
91         intent.putExtra("hasCapture", audioDevice.mHasCapture);
92         intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
93         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
94     }
95 
waitForAlsaFile(int card, int device, boolean capture)96     private boolean waitForAlsaFile(int card, int device, boolean capture) {
97         // These values were empirically determined.
98         final int kNumRetries = 5;
99         final int kSleepTime = 500; // ms
100         String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
101         File alsaDevFile = new File(alsaDevPath);
102         boolean exists = false;
103         for (int retry = 0; !exists && retry < kNumRetries; retry++) {
104             exists = alsaDevFile.exists();
105             if (!exists) {
106                 try {
107                     Thread.sleep(kSleepTime);
108                 } catch (IllegalThreadStateException ex) {
109                     Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
110                 } catch (java.lang.InterruptedException ex) {
111                     Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
112                 }
113             }
114         }
115 
116         return exists;
117     }
118 
deviceAdded(UsbDevice usbDevice)119     /* package */ void deviceAdded(UsbDevice usbDevice) {
120         // Is there an audio interface in there?
121         boolean isAudioDevice = false;
122 
123         // FIXME - handle multiple configurations?
124         int interfaceCount = usbDevice.getInterfaceCount();
125         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
126                 ntrfaceIndex++) {
127             UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
128             if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
129                 isAudioDevice = true;
130             }
131         }
132         if (!isAudioDevice) {
133             return;
134         }
135 
136         //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
137         // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
138         // clear why this works, or that it can be relied on going forward.  Needs further
139         // research.
140         AlsaCardsParser cardsParser = new AlsaCardsParser();
141         cardsParser.scan();
142         // cardsParser.Log();
143 
144         // But we need to parse the device to determine its capabilities.
145         AlsaDevicesParser devicesParser = new AlsaDevicesParser();
146         devicesParser.scan();
147         // devicesParser.Log();
148 
149         // The protocol for now will be to select the last-connected (highest-numbered)
150         // Alsa Card.
151         int card = cardsParser.getNumCardRecords() - 1;
152         int device = 0;
153 
154         boolean hasPlayback = devicesParser.hasPlaybackDevices(card);
155         boolean hasCapture = devicesParser.hasCaptureDevices(card);
156         boolean hasMidi = devicesParser.hasMIDIDevices(card);
157 
158         // Playback device file needed/present?
159         if (hasPlayback &&
160             !waitForAlsaFile(card, device, false)) {
161             return;
162         }
163 
164         // Capture device file needed/present?
165         if (hasCapture &&
166             !waitForAlsaFile(card, device, true)) {
167             return;
168         }
169 
170         if (DEBUG) {
171             Slog.d(TAG,
172                     "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
173         }
174 
175         AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
176         mAudioDevices.put(usbDevice, audioDevice);
177         sendDeviceNotification(audioDevice, true);
178     }
179 
deviceRemoved(UsbDevice device)180     /* package */ void deviceRemoved(UsbDevice device) {
181        if (DEBUG) {
182           Slog.d(TAG, "deviceRemoved(): " + device);
183         }
184 
185         AudioDevice audioDevice = mAudioDevices.remove(device);
186         if (audioDevice != null) {
187             sendDeviceNotification(audioDevice, false);
188         }
189     }
190 
dump(FileDescriptor fd, PrintWriter pw)191     public void dump(FileDescriptor fd, PrintWriter pw) {
192         pw.println("  USB AudioDevices:");
193         for (UsbDevice device : mAudioDevices.keySet()) {
194             pw.println("    " + device.getDeviceName() + ": " + mAudioDevices.get(device));
195         }
196     }
197 }
198