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