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 and
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.UsbDevice;
23 import android.media.IAudioService;
24 import android.media.midi.MidiDeviceInfo;
25 import android.os.Bundle;
26 import android.os.ServiceManager;
27 import android.provider.Settings;
28 import android.service.usb.UsbAlsaManagerProto;
29 import android.util.Slog;
30 
31 import com.android.internal.alsa.AlsaCardsParser;
32 import com.android.internal.util.dump.DualDumpOutputStream;
33 import com.android.server.usb.descriptors.UsbDescriptorParser;
34 
35 import libcore.io.IoUtils;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 
40 /**
41  * UsbAlsaManager manages USB audio and MIDI devices.
42  */
43 public final class UsbAlsaManager {
44     private static final String TAG = UsbAlsaManager.class.getSimpleName();
45     private static final boolean DEBUG = false;
46 
47     private static final String ALSA_DIRECTORY = "/dev/snd/";
48 
49     private final Context mContext;
50     private IAudioService mAudioService;
51     private final boolean mHasMidiFeature;
52 
53     private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
54 
55     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
56     // ALSA device when we are notified that its associated USB device has been removed.
57     private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
58     private UsbAlsaDevice mSelectedDevice;
59 
60     /**
61      * List of connected MIDI devices
62      */
63     private final HashMap<String, UsbMidiDevice>
64             mMidiDevices = new HashMap<String, UsbMidiDevice>();
65 
66     // UsbMidiDevice for USB peripheral mode (gadget) device
67     private UsbMidiDevice mPeripheralMidiDevice = null;
68 
UsbAlsaManager(Context context)69     /* package */ UsbAlsaManager(Context context) {
70         mContext = context;
71         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
72     }
73 
systemReady()74     public void systemReady() {
75         mAudioService = IAudioService.Stub.asInterface(
76                         ServiceManager.getService(Context.AUDIO_SERVICE));
77     }
78 
79     /**
80      * Select the AlsaDevice to be used for AudioService.
81      * AlsaDevice.start() notifies AudioService of it's connected state.
82      *
83      * @param alsaDevice The selected UsbAlsaDevice for system USB audio.
84      */
selectAlsaDevice(UsbAlsaDevice alsaDevice)85     private synchronized void selectAlsaDevice(UsbAlsaDevice alsaDevice) {
86         if (DEBUG) {
87             Slog.d(TAG, "selectAlsaDevice " + alsaDevice);
88         }
89 
90         if (mSelectedDevice != null) {
91             deselectAlsaDevice();
92         }
93 
94         // FIXME Does not yet handle the case where the setting is changed
95         // after device connection.  Ideally we should handle the settings change
96         // in SettingsObserver. Here we should log that a USB device is connected
97         // and disconnected with its address (card , device) and force the
98         // connection or disconnection when the setting changes.
99         int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(),
100                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
101         if (isDisabled != 0) {
102             return;
103         }
104 
105         mSelectedDevice = alsaDevice;
106         alsaDevice.start();
107     }
108 
deselectAlsaDevice()109     private synchronized void deselectAlsaDevice() {
110         if (mSelectedDevice != null) {
111             mSelectedDevice.stop();
112             mSelectedDevice = null;
113         }
114     }
115 
getAlsaDeviceListIndexFor(String deviceAddress)116     private int getAlsaDeviceListIndexFor(String deviceAddress) {
117         for (int index = 0; index < mAlsaDevices.size(); index++) {
118             if (mAlsaDevices.get(index).getDeviceAddress().equals(deviceAddress)) {
119                 return index;
120             }
121         }
122         return -1;
123     }
124 
removeAlsaDeviceFromList(String deviceAddress)125     private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
126         int index = getAlsaDeviceListIndexFor(deviceAddress);
127         if (index > -1) {
128             return mAlsaDevices.remove(index);
129         } else {
130             return null;
131         }
132     }
133 
selectDefaultDevice()134     /* package */ UsbAlsaDevice selectDefaultDevice() {
135         if (DEBUG) {
136             Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
137         }
138 
139         if (mAlsaDevices.size() > 0) {
140             UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
141             if (DEBUG) {
142                 Slog.d(TAG, "  alsaDevice:" + alsaDevice);
143             }
144             if (alsaDevice != null) {
145                 selectAlsaDevice(alsaDevice);
146             }
147             return alsaDevice;
148         } else {
149             return null;
150         }
151     }
152 
usbDeviceAdded(String deviceAddress, UsbDevice usbDevice, UsbDescriptorParser parser)153     /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
154             UsbDescriptorParser parser) {
155         if (DEBUG) {
156             Slog.d(TAG, "usbDeviceAdded(): " + usbDevice.getManufacturerName()
157                     + " nm:" + usbDevice.getProductName());
158         }
159 
160         // Scan the Alsa File Space
161         mCardsParser.scan();
162 
163         // Find the ALSA spec for this device address
164         AlsaCardsParser.AlsaCardRecord cardRec =
165                 mCardsParser.findCardNumFor(deviceAddress);
166         if (cardRec == null) {
167             return;
168         }
169 
170         // Add it to the devices list
171         boolean hasInput = parser.hasInput();
172         boolean hasOutput = parser.hasOutput();
173         if (DEBUG) {
174             Slog.d(TAG, "hasInput: " + hasInput + " hasOutput:" + hasOutput);
175         }
176         if (hasInput || hasOutput) {
177             boolean isInputHeadset = parser.isInputHeadset();
178             boolean isOutputHeadset = parser.isOutputHeadset();
179 
180             if (mAudioService == null) {
181                 Slog.e(TAG, "no AudioService");
182                 return;
183             }
184 
185             UsbAlsaDevice alsaDevice =
186                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
187                                       deviceAddress, hasOutput, hasInput,
188                                       isInputHeadset, isOutputHeadset);
189             if (alsaDevice != null) {
190                 alsaDevice.setDeviceNameAndDescription(
191                           cardRec.getCardName(), cardRec.getCardDescription());
192                 mAlsaDevices.add(0, alsaDevice);
193                 selectAlsaDevice(alsaDevice);
194             }
195         }
196 
197         // look for MIDI devices
198         boolean hasMidi = parser.hasMIDIInterface();
199         if (DEBUG) {
200             Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
201         }
202         if (hasMidi && mHasMidiFeature) {
203             int device = 0;
204             Bundle properties = new Bundle();
205             String manufacturer = usbDevice.getManufacturerName();
206             String product = usbDevice.getProductName();
207             String version = usbDevice.getVersion();
208             String name;
209             if (manufacturer == null || manufacturer.isEmpty()) {
210                 name = product;
211             } else if (product == null || product.isEmpty()) {
212                 name = manufacturer;
213             } else {
214                 name = manufacturer + " " + product;
215             }
216             properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
217             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
218             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
219             properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
220             properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
221                     usbDevice.getSerialNumber());
222             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
223             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
224             properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
225 
226             UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
227                     cardRec.getCardNum(), 0 /*device*/);
228             if (usbMidiDevice != null) {
229                 mMidiDevices.put(deviceAddress, usbMidiDevice);
230             }
231         }
232 
233         if (DEBUG) {
234             Slog.d(TAG, "deviceAdded() - done");
235         }
236     }
237 
usbDeviceRemoved(String deviceAddress )238     /* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) {
239         if (DEBUG) {
240             Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")");
241         }
242 
243         // Audio
244         UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
245         Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
246         if (alsaDevice != null && alsaDevice == mSelectedDevice) {
247             deselectAlsaDevice();
248             selectDefaultDevice(); // if there any external devices left, select one of them
249         }
250 
251         // MIDI
252         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
253         if (usbMidiDevice != null) {
254             Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
255             IoUtils.closeQuietly(usbMidiDevice);
256         }
257     }
258 
setPeripheralMidiState(boolean enabled, int card, int device)259    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
260         if (!mHasMidiFeature) {
261             return;
262         }
263 
264         if (enabled && mPeripheralMidiDevice == null) {
265             Bundle properties = new Bundle();
266             Resources r = mContext.getResources();
267             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
268                     com.android.internal.R.string.usb_midi_peripheral_name));
269             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
270                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
271             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
272                     com.android.internal.R.string.usb_midi_peripheral_product_name));
273             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
274             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
275             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
276         } else if (!enabled && mPeripheralMidiDevice != null) {
277             IoUtils.closeQuietly(mPeripheralMidiDevice);
278             mPeripheralMidiDevice = null;
279         }
280    }
281 
282     //
283     // Devices List
284     //
285 /*
286     //import java.util.ArrayList;
287     public ArrayList<UsbAudioDevice> getConnectedDevices() {
288         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
289         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
290             devices.add(entry.getValue());
291         }
292         return devices;
293     }
294 */
295 
296     /**
297      * Dump the USB alsa state.
298      */
dump(DualDumpOutputStream dump, String idName, long id)299     public void dump(DualDumpOutputStream dump, String idName, long id) {
300         long token = dump.start(idName, id);
301 
302         dump.write("cards_parser", UsbAlsaManagerProto.CARDS_PARSER, mCardsParser.getScanStatus());
303 
304         for (UsbAlsaDevice usbAlsaDevice : mAlsaDevices) {
305             usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
306         }
307 
308         for (String deviceAddr : mMidiDevices.keySet()) {
309             // A UsbMidiDevice does not have a handle to the UsbDevice anymore
310             mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
311                     UsbAlsaManagerProto.MIDI_DEVICES);
312         }
313 
314         dump.end(token);
315     }
316 
317 /*
318     public void logDevicesList(String title) {
319       if (DEBUG) {
320           for (HashMap.Entry<UsbDevice,UsbAlsaDevice> entry : mAudioDevices.entrySet()) {
321               Slog.i(TAG, "UsbDevice-------------------");
322               Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
323               Slog.i(TAG, "UsbAlsaDevice--------------");
324               Slog.i(TAG, "" + entry.getValue());
325           }
326       }
327     }
328 */
329 
330     // This logs a more terse (and more readable) version of the devices list
331 /*
332     public void logDevices(String title) {
333       if (DEBUG) {
334           Slog.i(TAG, title);
335           for (HashMap.Entry<UsbDevice,UsbAlsaDevice> entry : mAudioDevices.entrySet()) {
336               Slog.i(TAG, entry.getValue().toShortString());
337           }
338       }
339     }
340 */
341 
342 }
343