• 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 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.Arrays;
39 import java.util.HashMap;
40 import java.util.List;
41 
42 /**
43  * UsbAlsaManager manages USB audio and MIDI devices.
44  */
45 public final class UsbAlsaManager {
46     private static final String TAG = UsbAlsaManager.class.getSimpleName();
47     private static final boolean DEBUG = false;
48 
49     // Flag to turn on/off multi-peripheral select mode
50     // Set to true to have single-device-only mode
51     private static final boolean mIsSingleMode = true;
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 
61     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
62     // ALSA device when we are notified that its associated USB device has been removed.
63     private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
64     private UsbAlsaDevice mSelectedDevice;
65 
66     //
67     // Device Denylist
68     //
69     // This exists due to problems with Sony game controllers which present as an audio device
70     // even if no headset is connected and have no way to set the volume on the unit.
71     // Handle this by simply declining to use them as an audio device.
72     private static final int USB_VENDORID_SONY = 0x054C;
73     private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT1 = 0x05C4;
74     private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT2 = 0x09CC;
75 
76     private static final int USB_DENYLIST_OUTPUT = 0x0001;
77     private static final int USB_DENYLIST_INPUT  = 0x0002;
78 
79     private static class DenyListEntry {
80         final int mVendorId;
81         final int mProductId;
82         final int mFlags;
83 
DenyListEntry(int vendorId, int productId, int flags)84         DenyListEntry(int vendorId, int productId, int flags) {
85             mVendorId = vendorId;
86             mProductId = productId;
87             mFlags = flags;
88         }
89     }
90 
91     static final List<DenyListEntry> sDeviceDenylist = Arrays.asList(
92             new DenyListEntry(USB_VENDORID_SONY,
93                     USB_PRODUCTID_PS4CONTROLLER_ZCT1,
94                     USB_DENYLIST_OUTPUT),
95             new DenyListEntry(USB_VENDORID_SONY,
96                     USB_PRODUCTID_PS4CONTROLLER_ZCT2,
97                     USB_DENYLIST_OUTPUT));
98 
isDeviceDenylisted(int vendorId, int productId, int flags)99     private static boolean isDeviceDenylisted(int vendorId, int productId, int flags) {
100         for (DenyListEntry entry : sDeviceDenylist) {
101             if (entry.mVendorId == vendorId && entry.mProductId == productId) {
102                 // see if the type flag is set
103                 return (entry.mFlags & flags) != 0;
104             }
105         }
106 
107         return false;
108     }
109 
110     /**
111      * List of connected MIDI devices
112      */
113     private final HashMap<String, UsbMidiDevice>
114             mMidiDevices = new HashMap<String, UsbMidiDevice>();
115 
116     // UsbMidiDevice for USB peripheral mode (gadget) device
117     private UsbMidiDevice mPeripheralMidiDevice = null;
118 
UsbAlsaManager(Context context)119     /* package */ UsbAlsaManager(Context context) {
120         mContext = context;
121         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
122     }
123 
systemReady()124     public void systemReady() {
125         mAudioService = IAudioService.Stub.asInterface(
126                         ServiceManager.getService(Context.AUDIO_SERVICE));
127     }
128 
129     /**
130      * Select the AlsaDevice to be used for AudioService.
131      * AlsaDevice.start() notifies AudioService of it's connected state.
132      *
133      * @param alsaDevice The selected UsbAlsaDevice for system USB audio.
134      */
selectAlsaDevice(UsbAlsaDevice alsaDevice)135     private synchronized void selectAlsaDevice(UsbAlsaDevice alsaDevice) {
136         if (DEBUG) {
137             Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
138         }
139 
140         // This must be where an existing USB audio device is deselected.... (I think)
141         if (mIsSingleMode && mSelectedDevice != null) {
142             deselectAlsaDevice();
143         }
144 
145         // FIXME Does not yet handle the case where the setting is changed
146         // after device connection.  Ideally we should handle the settings change
147         // in SettingsObserver. Here we should log that a USB device is connected
148         // and disconnected with its address (card , device) and force the
149         // connection or disconnection when the setting changes.
150         int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(),
151                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
152         if (isDisabled != 0) {
153             return;
154         }
155 
156         mSelectedDevice = alsaDevice;
157         alsaDevice.start();
158         if (DEBUG) {
159             Slog.d(TAG, "selectAlsaDevice() - done.");
160         }
161     }
162 
deselectAlsaDevice()163     private synchronized void deselectAlsaDevice() {
164         if (DEBUG) {
165             Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
166         }
167         if (mSelectedDevice != null) {
168             mSelectedDevice.stop();
169             mSelectedDevice = null;
170         }
171     }
172 
getAlsaDeviceListIndexFor(String deviceAddress)173     private int getAlsaDeviceListIndexFor(String deviceAddress) {
174         for (int index = 0; index < mAlsaDevices.size(); index++) {
175             if (mAlsaDevices.get(index).getDeviceAddress().equals(deviceAddress)) {
176                 return index;
177             }
178         }
179         return -1;
180     }
181 
removeAlsaDeviceFromList(String deviceAddress)182     private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
183         int index = getAlsaDeviceListIndexFor(deviceAddress);
184         if (index > -1) {
185             return mAlsaDevices.remove(index);
186         } else {
187             return null;
188         }
189     }
190 
selectDefaultDevice()191     /* package */ UsbAlsaDevice selectDefaultDevice() {
192         if (DEBUG) {
193             Slog.d(TAG, "selectDefaultDevice()");
194         }
195 
196         if (mAlsaDevices.size() > 0) {
197             UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
198             if (DEBUG) {
199                 Slog.d(TAG, "  alsaDevice:" + alsaDevice);
200             }
201             if (alsaDevice != null) {
202                 selectAlsaDevice(alsaDevice);
203             }
204             return alsaDevice;
205         } else {
206             return null;
207         }
208     }
209 
usbDeviceAdded(String deviceAddress, UsbDevice usbDevice, UsbDescriptorParser parser)210     /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
211             UsbDescriptorParser parser) {
212         if (DEBUG) {
213             Slog.d(TAG, "usbDeviceAdded(): " + usbDevice.getManufacturerName()
214                     + " nm:" + usbDevice.getProductName());
215         }
216 
217         // Scan the Alsa File Space
218         mCardsParser.scan();
219 
220         // Find the ALSA spec for this device address
221         AlsaCardsParser.AlsaCardRecord cardRec =
222                 mCardsParser.findCardNumFor(deviceAddress);
223         if (cardRec == null) {
224             return;
225         }
226 
227         // Add it to the devices list
228         boolean hasInput = parser.hasInput()
229                 && !isDeviceDenylisted(usbDevice.getVendorId(), usbDevice.getProductId(),
230                         USB_DENYLIST_INPUT);
231         boolean hasOutput = parser.hasOutput()
232                 && !isDeviceDenylisted(usbDevice.getVendorId(), usbDevice.getProductId(),
233                         USB_DENYLIST_OUTPUT);
234         if (DEBUG) {
235             Slog.d(TAG, "hasInput: " + hasInput + " hasOutput:" + hasOutput);
236         }
237         if (hasInput || hasOutput) {
238             boolean isInputHeadset = parser.isInputHeadset();
239             boolean isOutputHeadset = parser.isOutputHeadset();
240 
241             if (mAudioService == null) {
242                 Slog.e(TAG, "no AudioService");
243                 return;
244             }
245 
246             UsbAlsaDevice alsaDevice =
247                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
248                                       deviceAddress, hasOutput, hasInput,
249                                       isInputHeadset, isOutputHeadset);
250             if (alsaDevice != null) {
251                 alsaDevice.setDeviceNameAndDescription(
252                           cardRec.getCardName(), cardRec.getCardDescription());
253                 mAlsaDevices.add(0, alsaDevice);
254                 selectAlsaDevice(alsaDevice);
255             }
256         }
257 
258         // look for MIDI devices
259         boolean hasMidi = parser.hasMIDIInterface();
260         if (DEBUG) {
261             Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
262         }
263         if (hasMidi && mHasMidiFeature) {
264             int device = 0;
265             Bundle properties = new Bundle();
266             String manufacturer = usbDevice.getManufacturerName();
267             String product = usbDevice.getProductName();
268             String version = usbDevice.getVersion();
269             String name;
270             if (manufacturer == null || manufacturer.isEmpty()) {
271                 name = product;
272             } else if (product == null || product.isEmpty()) {
273                 name = manufacturer;
274             } else {
275                 name = manufacturer + " " + product;
276             }
277             properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
278             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
279             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
280             properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
281             properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
282                     usbDevice.getSerialNumber());
283             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
284             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
285             properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
286 
287             UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
288                     cardRec.getCardNum(), 0 /*device*/);
289             if (usbMidiDevice != null) {
290                 mMidiDevices.put(deviceAddress, usbMidiDevice);
291             }
292         }
293 
294         logDevices("deviceAdded()");
295 
296         if (DEBUG) {
297             Slog.d(TAG, "deviceAdded() - done");
298         }
299     }
300 
usbDeviceRemoved(String deviceAddress )301     /* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) {
302         if (DEBUG) {
303             Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")");
304         }
305 
306         // Audio
307         UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
308         Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
309         if (alsaDevice != null && alsaDevice == mSelectedDevice) {
310             deselectAlsaDevice();
311             selectDefaultDevice(); // if there any external devices left, select one of them
312         }
313 
314         // MIDI
315         UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
316         if (usbMidiDevice != null) {
317             Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
318             IoUtils.closeQuietly(usbMidiDevice);
319         }
320 
321         logDevices("usbDeviceRemoved()");
322 
323     }
324 
setPeripheralMidiState(boolean enabled, int card, int device)325    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
326         if (!mHasMidiFeature) {
327             return;
328         }
329 
330         if (enabled && mPeripheralMidiDevice == null) {
331             Bundle properties = new Bundle();
332             Resources r = mContext.getResources();
333             properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
334                     com.android.internal.R.string.usb_midi_peripheral_name));
335             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
336                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
337             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
338                     com.android.internal.R.string.usb_midi_peripheral_product_name));
339             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
340             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
341             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
342         } else if (!enabled && mPeripheralMidiDevice != null) {
343             IoUtils.closeQuietly(mPeripheralMidiDevice);
344             mPeripheralMidiDevice = null;
345         }
346    }
347 
348     //
349     // Devices List
350     //
351 /*
352     //import java.util.ArrayList;
353     public ArrayList<UsbAudioDevice> getConnectedDevices() {
354         ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
355         for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
356             devices.add(entry.getValue());
357         }
358         return devices;
359     }
360 */
361 
362     /**
363      * Dump the USB alsa state.
364      */
365     // invoked with "adb shell dumpsys usb"
dump(DualDumpOutputStream dump, String idName, long id)366     public void dump(DualDumpOutputStream dump, String idName, long id) {
367         long token = dump.start(idName, id);
368 
369         dump.write("cards_parser", UsbAlsaManagerProto.CARDS_PARSER, mCardsParser.getScanStatus());
370 
371         for (UsbAlsaDevice usbAlsaDevice : mAlsaDevices) {
372             usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
373         }
374 
375         for (String deviceAddr : mMidiDevices.keySet()) {
376             // A UsbMidiDevice does not have a handle to the UsbDevice anymore
377             mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
378                     UsbAlsaManagerProto.MIDI_DEVICES);
379         }
380 
381         dump.end(token);
382     }
383 
logDevicesList(String title)384     public void logDevicesList(String title) {
385         if (DEBUG) {
386             Slog.i(TAG, title + "----------------");
387             for (UsbAlsaDevice alsaDevice : mAlsaDevices) {
388                 Slog.i(TAG, "  -->");
389                 Slog.i(TAG, "" + alsaDevice);
390                 Slog.i(TAG, "  <--");
391             }
392             Slog.i(TAG, "----------------");
393         }
394     }
395 
396     // This logs a more terse (and more readable) version of the devices list
logDevices(String title)397     public void logDevices(String title) {
398         if (DEBUG) {
399             Slog.i(TAG, title + "----------------");
400             for (UsbAlsaDevice alsaDevice : mAlsaDevices) {
401                 Slog.i(TAG, alsaDevice.toShortString());
402             }
403             Slog.i(TAG, "----------------");
404         }
405     }
406 }
407