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.FileObserver;
29 import android.os.Bundle;
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.io.FileDescriptor;
45 import java.io.PrintWriter;
46 import java.util.HashMap;
47 import java.util.ArrayList;
48 
49 /**
50  * UsbAlsaManager manages USB audio and MIDI devices.
51  */
52 public final class UsbAlsaManager {
53     private static final String TAG = UsbAlsaManager.class.getSimpleName();
54     private static final boolean DEBUG = false;
55 
56     private static final String ALSA_DIRECTORY = "/dev/snd/";
57 
58     private final Context mContext;
59     private IAudioService mAudioService;
60     private final boolean mHasMidiFeature;
61 
62     private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
63     private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
64 
65     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
66     // ALSA device when we are notified that its associated USB device has been removed.
67 
68     private final HashMap<UsbDevice,UsbAudioDevice>
69         mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
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 = (audioDevice == mAccessoryAudioDevice ?
191                         AudioSystem.DEVICE_OUT_USB_ACCESSORY :
192                         AudioSystem.DEVICE_OUT_USB_DEVICE);
193                 if (DEBUG) {
194                     Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
195                             " addr:" + address + " name:" + audioDevice.mDeviceName);
196                 }
197                 mAudioService.setWiredDeviceConnectionState(
198                         device, state, address, audioDevice.mDeviceName, TAG);
199             }
200 
201             // Capture Device
202             if (audioDevice.mHasCapture) {
203                int device = (audioDevice == mAccessoryAudioDevice ?
204                         AudioSystem.DEVICE_IN_USB_ACCESSORY :
205                         AudioSystem.DEVICE_IN_USB_DEVICE);
206                 mAudioService.setWiredDeviceConnectionState(
207                         device, state, address, audioDevice.mDeviceName, TAG);
208             }
209         } catch (RemoteException e) {
210             Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
211         }
212     }
213 
waitForAlsaDevice(int card, int device, int type)214     private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
215         if (DEBUG) {
216             Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")");
217         }
218 
219         AlsaDevice testDevice = new AlsaDevice(type, card, device);
220 
221         // This value was empirically determined.
222         final int kWaitTime = 2500; // ms
223 
224         synchronized(mAlsaDevices) {
225             long timeout = SystemClock.elapsedRealtime() + kWaitTime;
226             do {
227                 if (mAlsaDevices.values().contains(testDevice)) {
228                     return testDevice;
229                 }
230                 long waitTime = timeout - SystemClock.elapsedRealtime();
231                 if (waitTime > 0) {
232                     try {
233                         mAlsaDevices.wait(waitTime);
234                     } catch (InterruptedException e) {
235                         Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
236                     }
237                 }
238             } while (timeout > SystemClock.elapsedRealtime());
239         }
240 
241         Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
242         return null;
243     }
244 
alsaFileAdded(String name)245     private void alsaFileAdded(String name) {
246         int type = AlsaDevice.TYPE_UNKNOWN;
247         int card = -1, device = -1;
248 
249         if (name.startsWith("pcmC")) {
250             if (name.endsWith("p")) {
251                 type = AlsaDevice.TYPE_PLAYBACK;
252             } else if (name.endsWith("c")) {
253                 type = AlsaDevice.TYPE_CAPTURE;
254             }
255         } else if (name.startsWith("midiC")) {
256             type = AlsaDevice.TYPE_MIDI;
257         }
258 
259         if (type != AlsaDevice.TYPE_UNKNOWN) {
260             try {
261                 int c_index = name.indexOf('C');
262                 int d_index = name.indexOf('D');
263                 int end = name.length();
264                 if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
265                     // skip trailing 'p' or 'c'
266                     end--;
267                 }
268                 card = Integer.parseInt(name.substring(c_index + 1, d_index));
269                 device = Integer.parseInt(name.substring(d_index + 1, end));
270             } catch (Exception e) {
271                 Slog.e(TAG, "Could not parse ALSA file name " + name, e);
272                 return;
273             }
274             synchronized(mAlsaDevices) {
275                 if (mAlsaDevices.get(name) == null) {
276                     AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
277                     Slog.d(TAG, "Adding ALSA device " + alsaDevice);
278                     mAlsaDevices.put(name, alsaDevice);
279                     mAlsaDevices.notifyAll();
280                 }
281             }
282         }
283     }
284 
alsaFileRemoved(String path)285     private void alsaFileRemoved(String path) {
286         synchronized(mAlsaDevices) {
287             AlsaDevice device = mAlsaDevices.remove(path);
288             if (device != null) {
289                 Slog.d(TAG, "ALSA device removed: " + device);
290             }
291         }
292     }
293 
294     /*
295      * Select the default device of the specified card.
296      */
selectAudioCard(int card)297     /* package */ UsbAudioDevice selectAudioCard(int card) {
298         if (DEBUG) {
299             Slog.d(TAG, "selectAudioCard() card:" + card
300                     + " isCardUsb(): " + mCardsParser.isCardUsb(card));
301         }
302         if (!mCardsParser.isCardUsb(card)) {
303             // Don't. AudioPolicyManager has logic for falling back to internal devices.
304             return null;
305         }
306 
307         mDevicesParser.scan();
308         int device = mDevicesParser.getDefaultDeviceNum(card);
309 
310         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
311         boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
312         if (DEBUG) {
313             Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
314         }
315 
316         int deviceClass =
317             (mCardsParser.isCardUsb(card)
318                 ? UsbAudioDevice.kAudioDeviceClass_External
319                 : UsbAudioDevice.kAudioDeviceClass_Internal) |
320             UsbAudioDevice.kAudioDeviceMeta_Alsa;
321 
322         // Playback device file needed/present?
323         if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
324             return null;
325         }
326 
327         // Capture device file needed/present?
328         if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
329             return null;
330         }
331 
332         UsbAudioDevice audioDevice =
333                 new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
334         AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
335         audioDevice.mDeviceName = cardRecord.mCardName;
336         audioDevice.mDeviceDescription = cardRecord.mCardDescription;
337 
338         notifyDeviceState(audioDevice, true);
339 
340         return audioDevice;
341     }
342 
selectDefaultDevice()343     /* package */ UsbAudioDevice selectDefaultDevice() {
344         if (DEBUG) {
345             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
346         }
347         return selectAudioCard(mCardsParser.getDefaultCard());
348     }
349 
usbDeviceAdded(UsbDevice usbDevice)350     /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
351        if (DEBUG) {
352           Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
353                   " nm:" + usbDevice.getProductName());
354         }
355 
356         // Is there an audio interface in there?
357         boolean isAudioDevice = false;
358 
359         // FIXME - handle multiple configurations?
360         int interfaceCount = usbDevice.getInterfaceCount();
361         for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
362                 ntrfaceIndex++) {
363             UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
364             if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
365                 isAudioDevice = true;
366             }
367         }
368 
369         if (DEBUG) {
370             Slog.d(TAG, "  isAudioDevice: " + isAudioDevice);
371         }
372         if (!isAudioDevice) {
373             return;
374         }
375 
376         int addedCard = mCardsParser.getDefaultUsbCard();
377 
378         // If the default isn't a USB device, let the existing "select internal mechanism"
379         // handle the selection.
380         if (DEBUG) {
381             Slog.d(TAG, "  mCardsParser.isCardUsb(" + addedCard + ") = "
382                         + mCardsParser.isCardUsb(addedCard));
383         }
384         if (mCardsParser.isCardUsb(addedCard)) {
385             UsbAudioDevice audioDevice = selectAudioCard(addedCard);
386             if (audioDevice != null) {
387                 mAudioDevices.put(usbDevice, audioDevice);
388                 Slog.i(TAG, "USB Audio Device Added: " + audioDevice);
389             }
390 
391             // look for MIDI devices
392 
393             // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
394             // Uncomment this next line if that behavior changes in the fugure.
395             // mDevicesParser.scan()
396 
397             boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
398             if (hasMidi && mHasMidiFeature) {
399                 int device = mDevicesParser.getDefaultDeviceNum(addedCard);
400                 AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
401                 if (alsaDevice != null) {
402                     Bundle properties = new Bundle();
403                     String manufacturer = usbDevice.getManufacturerName();
404                     String product = usbDevice.getProductName();
405                     String version = usbDevice.getVersion();
406                     String name;
407                     if (manufacturer == null || manufacturer.isEmpty()) {
408                         name = product;
409                     } else if (product == null || product.isEmpty()) {
410                         name = manufacturer;
411                     } else {
412                         name = manufacturer + " " + product;
413                     }
414                     properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
415                     properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
416                     properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
417                     properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
418                     properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
419                             usbDevice.getSerialNumber());
420                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard);
421                     properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice);
422                     properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
423 
424                     UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
425                             alsaDevice.mCard, alsaDevice.mDevice);
426                     if (usbMidiDevice != null) {
427                         mMidiDevices.put(usbDevice, usbMidiDevice);
428                     }
429                 }
430             }
431         }
432 
433         if (DEBUG) {
434             Slog.d(TAG, "deviceAdded() - done");
435         }
436     }
437 
usbDeviceRemoved(UsbDevice usbDevice)438     /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
439         if (DEBUG) {
440           Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() +
441                   " " + usbDevice.getProductName());
442         }
443 
444         UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
445         Slog.i(TAG, "USB Audio Device Removed: " + audioDevice);
446         if (audioDevice != null) {
447             if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
448                 notifyDeviceState(audioDevice, false);
449 
450                 // if there any external devices left, select one of them
451                 selectDefaultDevice();
452             }
453         }
454         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
455         if (usbMidiDevice != null) {
456             IoUtils.closeQuietly(usbMidiDevice);
457         }
458     }
459 
setAccessoryAudioState(boolean enabled, int card, int device)460    /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) {
461        if (DEBUG) {
462             Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device);
463         }
464         if (enabled) {
465             mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
466                     UsbAudioDevice.kAudioDeviceClass_External);
467             notifyDeviceState(mAccessoryAudioDevice, true);
468         } else if (mAccessoryAudioDevice != null) {
469             notifyDeviceState(mAccessoryAudioDevice, false);
470             mAccessoryAudioDevice = null;
471         }
472     }
473 
setPeripheralMidiState(boolean enabled, int card, int device)474    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
475         if (!mHasMidiFeature) {
476             return;
477         }
478 
479         if (enabled && mPeripheralMidiDevice == null) {
480             Bundle properties = new Bundle();
481             Resources r = mContext.getResources();
482             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
483                     com.android.internal.R.string.usb_midi_peripheral_name));
484             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
485                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
486             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
487                     com.android.internal.R.string.usb_midi_peripheral_product_name));
488             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
489             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
490             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
491         } else if (!enabled && mPeripheralMidiDevice != null) {
492             IoUtils.closeQuietly(mPeripheralMidiDevice);
493             mPeripheralMidiDevice = null;
494         }
495    }
496 
497     //
498     // Devices List
499     //
getConnectedDevices()500     public ArrayList<UsbAudioDevice> getConnectedDevices() {
501         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
502         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
503             devices.add(entry.getValue());
504         }
505         return devices;
506     }
507 
508     //
509     // Logging
510     //
dump(IndentingPrintWriter pw)511     public void dump(IndentingPrintWriter pw) {
512         pw.println("USB Audio Devices:");
513         for (UsbDevice device : mAudioDevices.keySet()) {
514             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
515         }
516         pw.println("USB MIDI Devices:");
517         for (UsbDevice device : mMidiDevices.keySet()) {
518             pw.println("  " + device.getDeviceName() + ": " + mMidiDevices.get(device));
519         }
520     }
521 
logDevicesList(String title)522     public void logDevicesList(String title) {
523       if (DEBUG) {
524           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
525               Slog.i(TAG, "UsbDevice-------------------");
526               Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
527               Slog.i(TAG, "UsbAudioDevice--------------");
528               Slog.i(TAG, "" + entry.getValue());
529           }
530       }
531   }
532 
533   // This logs a more terse (and more readable) version of the devices list
logDevices(String title)534   public void logDevices(String title) {
535       if (DEBUG) {
536           Slog.i(TAG, title);
537           for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
538               Slog.i(TAG, entry.getValue().toShortString());
539           }
540       }
541   }
542 }
543