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