• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
20 import android.content.pm.PackageManager;
21 import android.content.res.Resources;
22 import android.hardware.usb.UsbConstants;
23 import android.hardware.usb.UsbDevice;
24 import android.hardware.usb.UsbInterface;
25 import android.media.AudioSystem;
26 import android.media.IAudioService;
27 import android.media.midi.MidiDeviceInfo;
28 import android.os.Bundle;
29 import android.os.FileObserver;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.SystemClock;
33 import android.provider.Settings;
34 import android.util.Slog;
35 
36 import com.android.internal.alsa.AlsaCardsParser;
37 import com.android.internal.alsa.AlsaDevicesParser;
38 import com.android.internal.util.IndentingPrintWriter;
39 import com.android.server.audio.AudioService;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.File;
44 import java.util.HashMap;
45 
46 /**
47  * UsbAlsaManager manages USB audio and MIDI devices.
48  */
49 public final class UsbAlsaManager {
50     private static final String TAG = UsbAlsaManager.class.getSimpleName();
51     private static final boolean DEBUG = false;
52 
53     private static final String ALSA_DIRECTORY = "/dev/snd/";
54 
55     private final Context mContext;
56     private IAudioService mAudioService;
57     private final boolean mHasMidiFeature;
58 
59     private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
60     private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
61 
62     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
63     // ALSA device when we are notified that its associated USB device has been removed.
64 
65     private final HashMap<UsbDevice,UsbAudioDevice>
66         mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
67 
68     private boolean mIsInputHeadset; // as reported by UsbDescriptorParser
69     private boolean mIsOutputHeadset; // as reported by UsbDescriptorParser
70 
71     private final HashMap<UsbDevice,UsbMidiDevice>
72         mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
73 
74     private final HashMap<String,AlsaDevice>
75         mAlsaDevices = new HashMap<String,AlsaDevice>();
76 
77     private UsbAudioDevice mAccessoryAudioDevice = null;
78 
79     // UsbMidiDevice for USB peripheral mode (gadget) device
80     private UsbMidiDevice mPeripheralMidiDevice = null;
81 
82     private final class AlsaDevice {
83         public static final int TYPE_UNKNOWN = 0;
84         public static final int TYPE_PLAYBACK = 1;
85         public static final int TYPE_CAPTURE = 2;
86         public static final int TYPE_MIDI = 3;
87 
88         public int mCard;
89         public int mDevice;
90         public int mType;
91 
AlsaDevice(int type, int card, int device)92         public AlsaDevice(int type, int card, int device) {
93             mType = type;
94             mCard = card;
95             mDevice = device;
96         }
97 
equals(Object obj)98         public boolean equals(Object obj) {
99             if (! (obj instanceof AlsaDevice)) {
100                 return false;
101             }
102             AlsaDevice other = (AlsaDevice)obj;
103             return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice);
104         }
105 
toString()106         public String toString() {
107             StringBuilder sb = new StringBuilder();
108             sb.append("AlsaDevice: [card: " + mCard);
109             sb.append(", device: " + mDevice);
110             sb.append(", type: " + mType);
111             sb.append("]");
112             return sb.toString();
113         }
114     }
115 
116     private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY,
117             FileObserver.CREATE | FileObserver.DELETE) {
118         public void onEvent(int event, String path) {
119             switch (event) {
120                 case FileObserver.CREATE:
121                     alsaFileAdded(path);
122                     break;
123                 case FileObserver.DELETE:
124                     alsaFileRemoved(path);
125                     break;
126             }
127         }
128     };
129 
UsbAlsaManager(Context context)130     /* package */ UsbAlsaManager(Context context) {
131         mContext = context;
132         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
133 
134         // initial scan
135         mCardsParser.scan();
136     }
137 
systemReady()138     public void systemReady() {
139         mAudioService = IAudioService.Stub.asInterface(
140                         ServiceManager.getService(Context.AUDIO_SERVICE));
141 
142         mAlsaObserver.startWatching();
143 
144         // add existing alsa devices
145         File[] files = new File(ALSA_DIRECTORY).listFiles();
146         if (files != null) {
147             for (int i = 0; i < files.length; i++) {
148                 alsaFileAdded(files[i].getName());
149             }
150         }
151     }
152 
153     // Notifies AudioService when a device is added or removed
154     // audioDevice - the AudioDevice that was added or removed
155     // enabled - if true, we're connecting a device (it's arrived), else disconnecting
notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled)156     private void notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled) {
157         if (DEBUG) {
158             Slog.d(TAG, "notifyDeviceState " + enabled + " " + audioDevice);
159         }
160 
161         if (mAudioService == null) {
162             Slog.e(TAG, "no AudioService");
163             return;
164         }
165 
166         // FIXME Does not yet handle the case where the setting is changed
167         // after device connection.  Ideally we should handle the settings change
168         // in SettingsObserver. Here we should log that a USB device is connected
169         // and disconnected with its address (card , device) and force the
170         // connection or disconnection when the setting changes.
171         int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(),
172                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
173         if (isDisabled != 0) {
174             return;
175         }
176 
177         int state = (enabled ? 1 : 0);
178         int alsaCard = audioDevice.mCard;
179         int alsaDevice = audioDevice.mDevice;
180         if (alsaCard < 0 || alsaDevice < 0) {
181             Slog.e(TAG, "Invalid alsa card or device alsaCard: " + alsaCard +
182                         " alsaDevice: " + alsaDevice);
183             return;
184         }
185 
186         String address = AudioService.makeAlsaAddressString(alsaCard, alsaDevice);
187         try {
188             // Playback Device
189             if (audioDevice.mHasPlayback) {
190                 int device;
191                 if (mIsOutputHeadset) {
192                     device = AudioSystem.DEVICE_OUT_USB_HEADSET;
193                 } else {
194                     device = (audioDevice == mAccessoryAudioDevice
195                         ? AudioSystem.DEVICE_OUT_USB_ACCESSORY
196                         : AudioSystem.DEVICE_OUT_USB_DEVICE);
197                 }
198                 if (DEBUG) {
199                     Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
200                             " addr:" + address + " name:" + audioDevice.getDeviceName());
201                 }
202                 mAudioService.setWiredDeviceConnectionState(
203                         device, state, address, audioDevice.getDeviceName(), TAG);
204             }
205 
206             // Capture Device
207             if (audioDevice.mHasCapture) {
208                 int device;
209                 if (mIsInputHeadset) {
210                     device = AudioSystem.DEVICE_IN_USB_HEADSET;
211                 } else {
212                     device = (audioDevice == mAccessoryAudioDevice
213                         ? AudioSystem.DEVICE_IN_USB_ACCESSORY
214                         : AudioSystem.DEVICE_IN_USB_DEVICE);
215                 }
216                 mAudioService.setWiredDeviceConnectionState(
217                         device, state, address, audioDevice.getDeviceName(), TAG);
218             }
219         } catch (RemoteException e) {
220             Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
221         }
222     }
223 
waitForAlsaDevice(int card, int device, int type)224     private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
225         if (DEBUG) {
226             Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")");
227         }
228 
229         AlsaDevice testDevice = new AlsaDevice(type, card, device);
230 
231         // This value was empirically determined.
232         final int kWaitTimeMs = 2500;
233 
234         synchronized(mAlsaDevices) {
235             long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs;
236             do {
237                 if (mAlsaDevices.values().contains(testDevice)) {
238                     return testDevice;
239                 }
240                 long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime();
241                 if (waitTimeMs > 0) {
242                     try {
243                         mAlsaDevices.wait(waitTimeMs);
244                     } catch (InterruptedException e) {
245                         Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
246                     }
247                 }
248             } while (timeoutMs > SystemClock.elapsedRealtime());
249         }
250 
251         Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
252         return null;
253     }
254 
alsaFileAdded(String name)255     private void alsaFileAdded(String name) {
256         int type = AlsaDevice.TYPE_UNKNOWN;
257         int card = -1, device = -1;
258 
259         if (name.startsWith("pcmC")) {
260             if (name.endsWith("p")) {
261                 type = AlsaDevice.TYPE_PLAYBACK;
262             } else if (name.endsWith("c")) {
263                 type = AlsaDevice.TYPE_CAPTURE;
264             }
265         } else if (name.startsWith("midiC")) {
266             type = AlsaDevice.TYPE_MIDI;
267         }
268 
269         if (type != AlsaDevice.TYPE_UNKNOWN) {
270             try {
271                 int c_index = name.indexOf('C');
272                 int d_index = name.indexOf('D');
273                 int end = name.length();
274                 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
275                     // skip trailing 'p' or 'c'
276                     end--;
277                 }
278                 card = Integer.parseInt(name.substring(c_index + 1, d_index));
279                 device = Integer.parseInt(name.substring(d_index + 1, end));
280             } catch (Exception e) {
281                 Slog.e(TAG, "Could not parse ALSA file name " + name, e);
282                 return;
283             }
284             synchronized(mAlsaDevices) {
285                 if (mAlsaDevices.get(name) == null) {
286                     AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
287                     Slog.d(TAG, "Adding ALSA device " + alsaDevice);
288                     mAlsaDevices.put(name, alsaDevice);
289                     mAlsaDevices.notifyAll();
290                 }
291             }
292         }
293     }
294 
alsaFileRemoved(String path)295     private void alsaFileRemoved(String path) {
296         synchronized(mAlsaDevices) {
297             AlsaDevice device = mAlsaDevices.remove(path);
298             if (device != null) {
299                 Slog.d(TAG, "ALSA device removed: " + device);
300             }
301         }
302     }
303 
304     /*
305      * Select the default device of the specified card.
306      */
selectAudioCard(int card)307     /* package */ UsbAudioDevice selectAudioCard(int card) {
308         if (DEBUG) {
309             Slog.d(TAG, "selectAudioCard() card:" + card
310                     + " isCardUsb(): " + mCardsParser.isCardUsb(card));
311         }
312         if (!mCardsParser.isCardUsb(card)) {
313             // Don't. AudioPolicyManager has logic for falling back to internal devices.
314             return null;
315         }
316 
317         mDevicesParser.scan();
318         int device = mDevicesParser.getDefaultDeviceNum(card);
319 
320         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
321         boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
322         if (DEBUG) {
323             Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
324         }
325 
326         int deviceClass =
327             (mCardsParser.isCardUsb(card)
328                 ? UsbAudioDevice.kAudioDeviceClass_External
329                 : UsbAudioDevice.kAudioDeviceClass_Internal) |
330             UsbAudioDevice.kAudioDeviceMeta_Alsa;
331 
332         // Playback device file needed/present?
333         if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
334             return null;
335         }
336 
337         // Capture device file needed/present?
338         if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
339             return null;
340         }
341 
342         UsbAudioDevice audioDevice =
343                 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
344         AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
345         audioDevice.setDeviceNameAndDescription(cardRecord.mCardName, cardRecord.mCardDescription);
346 
347         notifyDeviceState(audioDevice, true /*enabled*/);
348 
349         return audioDevice;
350     }
351 
selectDefaultDevice()352     /* package */ UsbAudioDevice selectDefaultDevice() {
353         if (DEBUG) {
354             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
355         }
356         return selectAudioCard(mCardsParser.getDefaultCard());
357     }
358 
usbDeviceAdded(UsbDevice usbDevice, boolean isInputHeadset, boolean isOutputHeadset)359     /* package */ void usbDeviceAdded(UsbDevice usbDevice,
360             boolean isInputHeadset, boolean isOutputHeadset) {
361         if (DEBUG) {
362             Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName()
363                     + " nm:" + usbDevice.getProductName());
364         }
365 
366         mIsInputHeadset = isInputHeadset;
367         mIsOutputHeadset = isOutputHeadset;
368 
369         // Is there an audio interface in there?
370         boolean isAudioDevice = false;
371 
372         // FIXME - handle multiple configurations?
373         int interfaceCount = usbDevice.getInterfaceCount();
374         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
375                 ntrfaceIndex++) {
376             UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
377             if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
378                 isAudioDevice = true;
379             }
380         }
381 
382         if (DEBUG) {
383             Slog.d(TAG, "  isAudioDevice: " + isAudioDevice);
384         }
385         if (!isAudioDevice) {
386             return;
387         }
388 
389         int addedCard = mCardsParser.getDefaultUsbCard();
390 
391         // If the default isn't a USB device, let the existing "select internal mechanism"
392         // handle the selection.
393         if (DEBUG) {
394             Slog.d(TAG, "  mCardsParser.isCardUsb(" + addedCard + ") = "
395                         + mCardsParser.isCardUsb(addedCard));
396         }
397         if (mCardsParser.isCardUsb(addedCard)) {
398             UsbAudioDevice audioDevice = selectAudioCard(addedCard);
399             if (audioDevice != null) {
400                 mAudioDevices.put(usbDevice, audioDevice);
401                 Slog.i(TAG, "USB Audio Device Added: " + audioDevice);
402             }
403 
404             // look for MIDI devices
405 
406             // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
407             // Uncomment this next line if that behavior changes in the fugure.
408             // mDevicesParser.scan()
409 
410             boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
411             if (hasMidi && mHasMidiFeature) {
412                 int device = mDevicesParser.getDefaultDeviceNum(addedCard);
413                 AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
414                 if (alsaDevice != null) {
415                     Bundle properties = new Bundle();
416                     String manufacturer = usbDevice.getManufacturerName();
417                     String product = usbDevice.getProductName();
418                     String version = usbDevice.getVersion();
419                     String name;
420                     if (manufacturer == null || manufacturer.isEmpty()) {
421                         name = product;
422                     } else if (product == null || product.isEmpty()) {
423                         name = manufacturer;
424                     } else {
425                         name = manufacturer + " " + product;
426                     }
427                     properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
428                     properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
429                     properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
430                     properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
431                     properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
432                             usbDevice.getSerialNumber());
433                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard);
434                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice);
435                     properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
436 
437                     UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
438                             alsaDevice.mCard, alsaDevice.mDevice);
439                     if (usbMidiDevice != null) {
440                         mMidiDevices.put(usbDevice, usbMidiDevice);
441                     }
442                 }
443             }
444         }
445 
446         if (DEBUG) {
447             Slog.d(TAG, "deviceAdded() - done");
448         }
449     }
450 
usbDeviceRemoved(UsbDevice usbDevice)451     /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
452         if (DEBUG) {
453           Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() +
454                   " " + usbDevice.getProductName());
455         }
456 
457         UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
458         Slog.i(TAG, "USB Audio Device Removed: " + audioDevice);
459         if (audioDevice != null) {
460             if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
461                 notifyDeviceState(audioDevice, false /*enabled*/);
462 
463                 // if there any external devices left, select one of them
464                 selectDefaultDevice();
465             }
466         }
467         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
468         if (usbMidiDevice != null) {
469             IoUtils.closeQuietly(usbMidiDevice);
470         }
471     }
472 
setAccessoryAudioState(boolean enabled, int card, int device)473    /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) {
474        if (DEBUG) {
475             Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device);
476         }
477         if (enabled) {
478             mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
479                     UsbAudioDevice.kAudioDeviceClass_External);
480             notifyDeviceState(mAccessoryAudioDevice, true /*enabled*/);
481         } else if (mAccessoryAudioDevice != null) {
482             notifyDeviceState(mAccessoryAudioDevice, false /*enabled*/);
483             mAccessoryAudioDevice = null;
484         }
485     }
486 
setPeripheralMidiState(boolean enabled, int card, int device)487    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
488         if (!mHasMidiFeature) {
489             return;
490         }
491 
492         if (enabled && mPeripheralMidiDevice == null) {
493             Bundle properties = new Bundle();
494             Resources r = mContext.getResources();
495             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
496                     com.android.internal.R.string.usb_midi_peripheral_name));
497             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
498                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
499             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
500                     com.android.internal.R.string.usb_midi_peripheral_product_name));
501             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
502             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
503             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
504         } else if (!enabled && mPeripheralMidiDevice != null) {
505             IoUtils.closeQuietly(mPeripheralMidiDevice);
506             mPeripheralMidiDevice = null;
507         }
508    }
509 
510     //
511     // Devices List
512     //
513 /*
514     //import java.util.ArrayList;
515     public ArrayList<UsbAudioDevice> getConnectedDevices() {
516         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
517         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
518             devices.add(entry.getValue());
519         }
520         return devices;
521     }
522 */
523 
524     //
525     // Logging
526     //
527     // called by UsbService.dump
dump(IndentingPrintWriter pw)528     public void dump(IndentingPrintWriter pw) {
529         pw.println("USB Audio Devices:");
530         for (UsbDevice device : mAudioDevices.keySet()) {
531             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
532         }
533         pw.println("USB MIDI Devices:");
534         for (UsbDevice device : mMidiDevices.keySet()) {
535             pw.println("  " + device.getDeviceName() + ": " + mMidiDevices.get(device));
536         }
537     }
538 
539 /*
540     public void logDevicesList(String title) {
541       if (DEBUG) {
542           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
543               Slog.i(TAG, "UsbDevice-------------------");
544               Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
545               Slog.i(TAG, "UsbAudioDevice--------------");
546               Slog.i(TAG, "" + entry.getValue());
547           }
548       }
549     }
550 */
551 
552     // This logs a more terse (and more readable) version of the devices list
553 /*
554     public void logDevices(String title) {
555       if (DEBUG) {
556           Slog.i(TAG, title);
557           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
558               Slog.i(TAG, entry.getValue().toShortString());
559           }
560       }
561     }
562 */
563 
564 }
565