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.annotation.NonNull; 20 import android.content.Context; 21 import android.media.midi.MidiDeviceInfo; 22 import android.media.midi.MidiDeviceServer; 23 import android.media.midi.MidiDeviceStatus; 24 import android.media.midi.MidiManager; 25 import android.media.midi.MidiReceiver; 26 import android.os.Bundle; 27 import android.service.usb.UsbAlsaMidiDeviceProto; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.system.OsConstants; 31 import android.system.StructPollfd; 32 import android.util.Log; 33 34 import com.android.internal.midi.MidiEventScheduler; 35 import com.android.internal.midi.MidiEventScheduler.MidiEvent; 36 import com.android.internal.util.dump.DualDumpOutputStream; 37 38 import libcore.io.IoUtils; 39 40 import java.io.Closeable; 41 import java.io.FileDescriptor; 42 import java.io.FileInputStream; 43 import java.io.FileOutputStream; 44 import java.io.IOException; 45 46 /** 47 * Opens device connections to MIDI 1.0 endpoints. 48 * These endpoints will use ALSA. 49 */ 50 public final class UsbAlsaMidiDevice implements Closeable { 51 private static final String TAG = "UsbAlsaMidiDevice"; 52 53 private final int mAlsaCard; 54 private final int mAlsaDevice; 55 // USB outputs are MIDI inputs 56 private final InputReceiverProxy[] mMidiInputPortReceivers; 57 private final int mNumInputs; 58 private final int mNumOutputs; 59 60 private MidiDeviceServer mServer; 61 62 // event schedulers for each input port of the physical device 63 private MidiEventScheduler[] mEventSchedulers; 64 65 private static final int BUFFER_SIZE = 512; 66 67 private FileDescriptor[] mFileDescriptors; 68 69 // for polling multiple FileDescriptors for MIDI events 70 private StructPollfd[] mPollFDs; 71 // streams for reading from ALSA driver 72 private FileInputStream[] mInputStreams; 73 // streams for writing to ALSA driver 74 private FileOutputStream[] mOutputStreams; 75 76 private final Object mLock = new Object(); 77 private boolean mIsOpen; 78 private boolean mServerAvailable; 79 80 // pipe file descriptor for signalling input thread to exit 81 // only accessed from JNI code 82 private int mPipeFD = -1; 83 84 private PowerBoostSetter mPowerBoostSetter = null; 85 86 private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { 87 @Override 88 public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { 89 MidiDeviceInfo deviceInfo = status.getDeviceInfo(); 90 int numInputPorts = deviceInfo.getInputPortCount(); 91 int numOutputPorts = deviceInfo.getOutputPortCount(); 92 int numOpenPorts = 0; 93 94 for (int i = 0; i < numInputPorts; i++) { 95 if (status.isInputPortOpen(i)) { 96 numOpenPorts++; 97 } 98 } 99 100 for (int i = 0; i < numOutputPorts; i++) { 101 if (status.getOutputPortOpenCount(i) > 0) { 102 numOpenPorts += status.getOutputPortOpenCount(i); 103 } 104 } 105 106 synchronized (mLock) { 107 Log.d(TAG, "numOpenPorts: " + numOpenPorts + " isOpen: " + mIsOpen 108 + " mServerAvailable: " + mServerAvailable); 109 if ((numOpenPorts > 0) && !mIsOpen && mServerAvailable) { 110 openLocked(); 111 } else if ((numOpenPorts == 0) && mIsOpen) { 112 closeLocked(); 113 } 114 } 115 } 116 117 @Override 118 public void onClose() { 119 } 120 }; 121 122 // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist 123 // until the device has active clients 124 private final class InputReceiverProxy extends MidiReceiver { 125 private MidiReceiver mReceiver; 126 127 @Override onSend(byte[] msg, int offset, int count, long timestamp)128 public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { 129 MidiReceiver receiver = mReceiver; 130 if (receiver != null) { 131 receiver.send(msg, offset, count, timestamp); 132 } 133 } 134 setReceiver(MidiReceiver receiver)135 public void setReceiver(MidiReceiver receiver) { 136 mReceiver = receiver; 137 } 138 139 @Override onFlush()140 public void onFlush() throws IOException { 141 MidiReceiver receiver = mReceiver; 142 if (receiver != null) { 143 receiver.flush(); 144 } 145 } 146 } 147 148 /** 149 * Creates an UsbAlsaMidiDevice based on the input parameters. Read/Write streams 150 * will be created individually as some devices don't have the same number of 151 * inputs and outputs. 152 */ create(Context context, Bundle properties, int card, int device, int numInputs, int numOutputs)153 public static UsbAlsaMidiDevice create(Context context, Bundle properties, int card, 154 int device, int numInputs, int numOutputs) { 155 UsbAlsaMidiDevice midiDevice = new UsbAlsaMidiDevice(card, device, numInputs, numOutputs); 156 if (!midiDevice.register(context, properties)) { 157 IoUtils.closeQuietly(midiDevice); 158 Log.e(TAG, "createDeviceServer failed"); 159 return null; 160 } 161 return midiDevice; 162 } 163 UsbAlsaMidiDevice(int card, int device, int numInputs, int numOutputs)164 private UsbAlsaMidiDevice(int card, int device, int numInputs, int numOutputs) { 165 mAlsaCard = card; 166 mAlsaDevice = device; 167 mNumInputs = numInputs; 168 mNumOutputs = numOutputs; 169 170 // Create MIDI port receivers based on the number of output ports. The 171 // output of USB is the input of MIDI. 172 mMidiInputPortReceivers = new InputReceiverProxy[numOutputs]; 173 for (int port = 0; port < numOutputs; port++) { 174 mMidiInputPortReceivers[port] = new InputReceiverProxy(); 175 } 176 177 mPowerBoostSetter = new PowerBoostSetter(); 178 } 179 openLocked()180 private boolean openLocked() { 181 int inputStreamCount = mNumInputs; 182 // Create an extra stream for unblocking Os.poll() 183 if (inputStreamCount > 0) { 184 inputStreamCount++; 185 } 186 int outputStreamCount = mNumOutputs; 187 188 // The resulting file descriptors will be O_RDONLY following by O_WRONLY 189 FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, 190 inputStreamCount, outputStreamCount); 191 if (fileDescriptors == null) { 192 Log.e(TAG, "nativeOpen failed"); 193 return false; 194 } 195 mFileDescriptors = fileDescriptors; 196 197 mPollFDs = new StructPollfd[inputStreamCount]; 198 mInputStreams = new FileInputStream[inputStreamCount]; 199 200 for (int i = 0; i < inputStreamCount; i++) { 201 FileDescriptor fd = fileDescriptors[i]; 202 StructPollfd pollfd = new StructPollfd(); 203 pollfd.fd = fd; 204 pollfd.events = (short) OsConstants.POLLIN; 205 mPollFDs[i] = pollfd; 206 mInputStreams[i] = new FileInputStream(fd); 207 } 208 209 mOutputStreams = new FileOutputStream[outputStreamCount]; 210 mEventSchedulers = new MidiEventScheduler[outputStreamCount]; 211 212 int curOutputStream = 0; 213 for (int i = 0; i < outputStreamCount; i++) { 214 mOutputStreams[i] = new FileOutputStream(fileDescriptors[inputStreamCount + i]); 215 MidiEventScheduler scheduler = new MidiEventScheduler(); 216 mEventSchedulers[i] = scheduler; 217 mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver()); 218 } 219 220 final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 221 222 if (inputStreamCount > 0) { 223 // Create input thread which will read from all output ports of the physical device 224 new Thread("UsbAlsaMidiDevice input thread") { 225 @Override 226 public void run() { 227 byte[] buffer = new byte[BUFFER_SIZE]; 228 try { 229 while (true) { 230 // Record time of event immediately after waking. 231 long timestamp = System.nanoTime(); 232 synchronized (mLock) { 233 if (!mIsOpen) break; 234 235 // look for a readable FileDescriptor 236 for (int index = 0; index < mPollFDs.length; index++) { 237 StructPollfd pfd = mPollFDs[index]; 238 if ((pfd.revents & (OsConstants.POLLERR 239 | OsConstants.POLLHUP)) != 0) { 240 break; 241 } else if ((pfd.revents & OsConstants.POLLIN) != 0) { 242 // clear readable flag 243 pfd.revents = 0; 244 if (index == mInputStreams.length - 1) { 245 // last fd is used only for unblocking Os.poll() 246 break; 247 } 248 249 int count = mInputStreams[index].read(buffer); 250 outputReceivers[index].send(buffer, 0, count, timestamp); 251 252 // If messages are more than size 1, boost power. 253 if (mPowerBoostSetter != null && count > 1) { 254 mPowerBoostSetter.boostPower(); 255 } 256 } 257 } 258 } 259 260 // wait until we have a readable port or we are signalled to close 261 Os.poll(mPollFDs, -1 /* infinite timeout */); 262 } 263 } catch (IOException e) { 264 Log.d(TAG, "reader thread exiting"); 265 } catch (ErrnoException e) { 266 Log.d(TAG, "reader thread exiting"); 267 } 268 Log.d(TAG, "input thread exit"); 269 } 270 }.start(); 271 } 272 273 // Create output thread for each input port of the physical device 274 for (int port = 0; port < outputStreamCount; port++) { 275 final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; 276 final FileOutputStream outputStreamF = mOutputStreams[port]; 277 final int portF = port; 278 279 new Thread("UsbAlsaMidiDevice output thread " + port) { 280 @Override 281 public void run() { 282 while (true) { 283 MidiEvent event; 284 try { 285 event = (MidiEvent) eventSchedulerF.waitNextEvent(); 286 } catch (InterruptedException e) { 287 // try again 288 continue; 289 } 290 if (event == null) { 291 break; 292 } 293 try { 294 outputStreamF.write(event.data, 0, event.count); 295 } catch (IOException e) { 296 Log.e(TAG, "write failed for port " + portF); 297 } 298 eventSchedulerF.addEventToPool(event); 299 } 300 Log.d(TAG, "output thread exit"); 301 } 302 }.start(); 303 } 304 305 mIsOpen = true; 306 return true; 307 } 308 register(Context context, Bundle properties)309 private boolean register(Context context, Bundle properties) { 310 MidiManager midiManager = context.getSystemService(MidiManager.class); 311 if (midiManager == null) { 312 Log.e(TAG, "No MidiManager in UsbAlsaMidiDevice.register()"); 313 return false; 314 } 315 316 mServerAvailable = true; 317 mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, 318 null, null, properties, MidiDeviceInfo.TYPE_USB, 319 MidiDeviceInfo.PROTOCOL_UNKNOWN, mCallback); 320 if (mServer == null) { 321 return false; 322 } 323 324 return true; 325 } 326 327 @Override close()328 public void close() throws IOException { 329 synchronized (mLock) { 330 if (mIsOpen) { 331 closeLocked(); 332 } 333 mServerAvailable = false; 334 } 335 336 if (mServer != null) { 337 IoUtils.closeQuietly(mServer); 338 } 339 } 340 closeLocked()341 private void closeLocked() { 342 for (int i = 0; i < mEventSchedulers.length; i++) { 343 mMidiInputPortReceivers[i].setReceiver(null); 344 mEventSchedulers[i].close(); 345 } 346 mEventSchedulers = null; 347 348 for (int i = 0; i < mInputStreams.length; i++) { 349 IoUtils.closeQuietly(mInputStreams[i]); 350 } 351 mInputStreams = null; 352 353 for (int i = 0; i < mOutputStreams.length; i++) { 354 IoUtils.closeQuietly(mOutputStreams[i]); 355 } 356 mOutputStreams = null; 357 358 // nativeClose will close the file descriptors and signal the input thread to exit 359 nativeClose(mFileDescriptors); 360 mFileDescriptors = null; 361 362 mIsOpen = false; 363 } 364 365 /** 366 * Write a description of the device to a dump stream. 367 */ dump(String deviceAddr, @NonNull DualDumpOutputStream dump, @NonNull String idName, long id)368 public void dump(String deviceAddr, @NonNull DualDumpOutputStream dump, @NonNull String idName, 369 long id) { 370 long token = dump.start(idName, id); 371 372 dump.write("device_address", UsbAlsaMidiDeviceProto.DEVICE_ADDRESS, deviceAddr); 373 dump.write("card", UsbAlsaMidiDeviceProto.CARD, mAlsaCard); 374 dump.write("device", UsbAlsaMidiDeviceProto.DEVICE, mAlsaDevice); 375 376 dump.end(token); 377 } 378 nativeOpen(int card, int device, int numInputs, int numOutputs)379 private native FileDescriptor[] nativeOpen(int card, int device, int numInputs, 380 int numOutputs); nativeClose(FileDescriptor[] fileDescriptors)381 private native void nativeClose(FileDescriptor[] fileDescriptors); 382 } 383