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