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 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 127 public void setReceiver(MidiReceiver receiver) { 128 mReceiver = receiver; 129 } 130 } 131 132 public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { 133 // FIXME - support devices with different number of input and output ports 134 int subDeviceCount = nativeGetSubdeviceCount(card, device); 135 if (subDeviceCount <= 0) { 136 Log.e(TAG, "nativeGetSubdeviceCount failed"); 137 return null; 138 } 139 140 UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); 141 if (!midiDevice.register(context, properties)) { 142 IoUtils.closeQuietly(midiDevice); 143 Log.e(TAG, "createDeviceServer failed"); 144 return null; 145 } 146 return midiDevice; 147 } 148 149 private UsbMidiDevice(int card, int device, int subdeviceCount) { 150 mAlsaCard = card; 151 mAlsaDevice = device; 152 mSubdeviceCount = subdeviceCount; 153 154 // FIXME - support devices with different number of input and output ports 155 int inputCount = subdeviceCount; 156 mInputPortReceivers = new InputReceiverProxy[inputCount]; 157 for (int port = 0; port < inputCount; port++) { 158 mInputPortReceivers[port] = new InputReceiverProxy(); 159 } 160 } 161 162 private boolean openLocked() { 163 // FIXME - support devices with different number of input and output ports 164 FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); 165 if (fileDescriptors == null) { 166 Log.e(TAG, "nativeOpen failed"); 167 return false; 168 } 169 170 mFileDescriptors = fileDescriptors; 171 int inputCount = fileDescriptors.length; 172 // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() 173 // in our input thread 174 int outputCount = fileDescriptors.length - 1; 175 176 mPollFDs = new StructPollfd[inputCount]; 177 mInputStreams = new FileInputStream[inputCount]; 178 for (int i = 0; i < inputCount; i++) { 179 FileDescriptor fd = fileDescriptors[i]; 180 StructPollfd pollfd = new StructPollfd(); 181 pollfd.fd = fd; 182 pollfd.events = (short)OsConstants.POLLIN; 183 mPollFDs[i] = pollfd; 184 mInputStreams[i] = new FileInputStream(fd); 185 } 186 187 mOutputStreams = new FileOutputStream[outputCount]; 188 mEventSchedulers = new MidiEventScheduler[outputCount]; 189 for (int i = 0; i < outputCount; i++) { 190 mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); 191 192 MidiEventScheduler scheduler = new MidiEventScheduler(); 193 mEventSchedulers[i] = scheduler; 194 mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); 195 } 196 197 final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 198 199 // Create input thread which will read from all input ports 200 new Thread("UsbMidiDevice input thread") { 201 @Override 202 public void run() { 203 byte[] buffer = new byte[BUFFER_SIZE]; 204 try { 205 while (true) { 206 // Record time of event immediately after waking. 207 long timestamp = System.nanoTime(); 208 synchronized (mLock) { 209 if (!mIsOpen) break; 210 211 // look for a readable FileDescriptor 212 for (int index = 0; index < mPollFDs.length; index++) { 213 StructPollfd pfd = mPollFDs[index]; 214 if ((pfd.revents & (OsConstants.POLLERR 215 | OsConstants.POLLHUP)) != 0) { 216 break; 217 } else if ((pfd.revents & OsConstants.POLLIN) != 0) { 218 // clear readable flag 219 pfd.revents = 0; 220 221 if (index == mInputStreams.length - 1) { 222 // last file descriptor is used only for unblocking Os.poll() 223 break; 224 } 225 226 int count = mInputStreams[index].read(buffer); 227 outputReceivers[index].send(buffer, 0, count, timestamp); 228 } 229 } 230 } 231 232 // wait until we have a readable port or we are signalled to close 233 Os.poll(mPollFDs, -1 /* infinite timeout */); 234 } 235 } catch (IOException e) { 236 Log.d(TAG, "reader thread exiting"); 237 } catch (ErrnoException e) { 238 Log.d(TAG, "reader thread exiting"); 239 } 240 Log.d(TAG, "input thread exit"); 241 } 242 }.start(); 243 244 // Create output thread for each output port 245 for (int port = 0; port < outputCount; port++) { 246 final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; 247 final FileOutputStream outputStreamF = mOutputStreams[port]; 248 final int portF = port; 249 250 new Thread("UsbMidiDevice output thread " + port) { 251 @Override 252 public void run() { 253 while (true) { 254 MidiEvent event; 255 try { 256 event = (MidiEvent)eventSchedulerF.waitNextEvent(); 257 } catch (InterruptedException e) { 258 // try again 259 continue; 260 } 261 if (event == null) { 262 break; 263 } 264 try { 265 outputStreamF.write(event.data, 0, event.count); 266 } catch (IOException e) { 267 Log.e(TAG, "write failed for port " + portF); 268 } 269 eventSchedulerF.addEventToPool(event); 270 } 271 Log.d(TAG, "output thread exit"); 272 } 273 }.start(); 274 } 275 276 mIsOpen = true; 277 return true; 278 } 279 280 private boolean register(Context context, Bundle properties) { 281 MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 282 if (midiManager == null) { 283 Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); 284 return false; 285 } 286 287 mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, 288 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); 289 if (mServer == null) { 290 return false; 291 } 292 293 return true; 294 } 295 296 @Override 297 public void close() throws IOException { 298 synchronized (mLock) { 299 if (mIsOpen) { 300 closeLocked(); 301 } 302 } 303 304 if (mServer != null) { 305 IoUtils.closeQuietly(mServer); 306 } 307 } 308 309 private void closeLocked() { 310 for (int i = 0; i < mEventSchedulers.length; i++) { 311 mInputPortReceivers[i].setReceiver(null); 312 mEventSchedulers[i].close(); 313 } 314 mEventSchedulers = null; 315 316 for (int i = 0; i < mInputStreams.length; i++) { 317 IoUtils.closeQuietly(mInputStreams[i]); 318 } 319 mInputStreams = null; 320 321 for (int i = 0; i < mOutputStreams.length; i++) { 322 IoUtils.closeQuietly(mOutputStreams[i]); 323 } 324 mOutputStreams = null; 325 326 // nativeClose will close the file descriptors and signal the input thread to exit 327 nativeClose(mFileDescriptors); 328 mFileDescriptors = null; 329 330 mIsOpen = false; 331 } 332 333 private static native int nativeGetSubdeviceCount(int card, int device); 334 private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); 335 private native void nativeClose(FileDescriptor[] fileDescriptors); 336 } 337