diff options
-rw-r--r-- | services/core/jni/com_android_server_UsbMidiDevice.cpp | 30 | ||||
-rw-r--r-- | services/usb/java/com/android/server/usb/UsbMidiDevice.java | 229 |
2 files changed, 196 insertions, 63 deletions
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp index cb70144..06b9bc3 100644 --- a/services/core/jni/com_android_server_UsbMidiDevice.cpp +++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp @@ -36,6 +36,7 @@ namespace android { static jclass sFileDescriptorClass; +static jfieldID sPipeFDField; static jint android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, @@ -66,14 +67,15 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */ } static jobjectArray -android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device, +android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint device, jint subdevice_count) { char path[100]; snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device); - jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL); + // allocate one extra file descriptor for close pipe + jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL); if (!fds) { return NULL; } @@ -91,12 +93,27 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, ji env->DeleteLocalRef(fileDescriptor); } + // create a pipe to use for unblocking our input thread + int pipeFD[2]; + pipe(pipeFD); + jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]); + env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor); + env->DeleteLocalRef(fileDescriptor); + // store our end of the pipe in mPipeFD + env->SetIntField(thiz, sPipeFDField, pipeFD[1]); + return fds; } static void -android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds) +android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) { + // write to mPipeFD to unblock input thread + jint pipeFD = env->GetIntField(thiz, sPipeFDField); + write(pipeFD, &pipeFD, sizeof(pipeFD)); + close(pipeFD); + env->SetIntField(thiz, sPipeFDField, -1); + int count = env->GetArrayLength(fds); for (int i = 0; i < count; i++) { jobject fd = env->GetObjectArrayElement(fds, i); @@ -117,13 +134,18 @@ int register_android_server_UsbMidiDevice(JNIEnv *env) ALOGE("Can't find java/io/FileDescriptor"); return -1; } - sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);; + sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz); clazz = env->FindClass("com/android/server/usb/UsbMidiDevice"); if (clazz == NULL) { ALOGE("Can't find com/android/server/usb/UsbMidiDevice"); return -1; } + sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I"); + if (sPipeFDField == NULL) { + ALOGE("Can't find UsbMidiDevice.mPipeFD"); + return -1; + } return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice", method_table, NELEM(method_table)); diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index 671cf01..97bf505 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -19,6 +19,7 @@ package com.android.server.usb; import android.content.Context; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; import android.media.midi.MidiManager; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; @@ -43,38 +44,100 @@ import java.io.IOException; public final class UsbMidiDevice implements Closeable { private static final String TAG = "UsbMidiDevice"; + private final int mAlsaCard; + private final int mAlsaDevice; + private final int mSubdeviceCount; + private final InputReceiverProxy[] mInputPortReceivers; + private MidiDeviceServer mServer; // event schedulers for each output port - private final MidiEventScheduler[] mEventSchedulers; + private MidiEventScheduler[] mEventSchedulers; private static final int BUFFER_SIZE = 512; - private final FileDescriptor[] mFileDescriptors; + private FileDescriptor[] mFileDescriptors; // for polling multiple FileDescriptors for MIDI events - private final StructPollfd[] mPollFDs; + private StructPollfd[] mPollFDs; // streams for reading from ALSA driver - private final FileInputStream[] mInputStreams; + private FileInputStream[] mInputStreams; // streams for writing to ALSA driver - private final FileOutputStream[] mOutputStreams; + private FileOutputStream[] mOutputStreams; - public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { - // FIXME - support devices with different number of input and output ports - int subDevices = nativeGetSubdeviceCount(card, device); - if (subDevices <= 0) { - Log.e(TAG, "nativeGetSubdeviceCount failed"); - return null; + private final Object mLock = new Object(); + private boolean mIsOpen; + + // pipe file descriptor for signalling input thread to exit + // only accessed from JNI code + private int mPipeFD = -1; + + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { + + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + MidiDeviceInfo deviceInfo = status.getDeviceInfo(); + int inputPorts = deviceInfo.getInputPortCount(); + int outputPorts = deviceInfo.getOutputPortCount(); + boolean hasOpenPorts = false; + + for (int i = 0; i < inputPorts; i++) { + if (status.isInputPortOpen(i)) { + hasOpenPorts = true; + break; + } + } + + if (!hasOpenPorts) { + for (int i = 0; i < outputPorts; i++) { + if (status.getOutputPortOpenCount(i) > 0) { + hasOpenPorts = true; + break; + } + } + } + + synchronized (mLock) { + if (hasOpenPorts && !mIsOpen) { + openLocked(); + } else if (!hasOpenPorts && mIsOpen) { + closeLocked(); + } + } + } + + @Override + public void onClose() { + } + }; + + // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist + // until the device has active clients + private final class InputReceiverProxy extends MidiReceiver { + private MidiReceiver mReceiver; + + @Override + public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.send(msg, offset, count, timestamp); + } } + public void setReceiver(MidiReceiver receiver) { + mReceiver = receiver; + } + } + + public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // FIXME - support devices with different number of input and output ports - FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices); - if (fileDescriptors == null) { - Log.e(TAG, "nativeOpen failed"); + int subDeviceCount = nativeGetSubdeviceCount(card, device); + if (subDeviceCount <= 0) { + Log.e(TAG, "nativeGetSubdeviceCount failed"); return null; } - UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); + UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); if (!midiDevice.register(context, properties)) { IoUtils.closeQuietly(midiDevice); Log.e(TAG, "createDeviceServer failed"); @@ -83,10 +146,32 @@ public final class UsbMidiDevice implements Closeable { return midiDevice; } - private UsbMidiDevice(FileDescriptor[] fileDescriptors) { + private UsbMidiDevice(int card, int device, int subdeviceCount) { + mAlsaCard = card; + mAlsaDevice = device; + mSubdeviceCount = subdeviceCount; + + // FIXME - support devices with different number of input and output ports + int inputCount = subdeviceCount; + mInputPortReceivers = new InputReceiverProxy[inputCount]; + for (int port = 0; port < inputCount; port++) { + mInputPortReceivers[port] = new InputReceiverProxy(); + } + } + + private boolean openLocked() { + // FIXME - support devices with different number of input and output ports + FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); + if (fileDescriptors == null) { + Log.e(TAG, "nativeOpen failed"); + return false; + } + mFileDescriptors = fileDescriptors; int inputCount = fileDescriptors.length; - int outputCount = fileDescriptors.length; + // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() + // in our input thread + int outputCount = fileDescriptors.length - 1; mPollFDs = new StructPollfd[inputCount]; mInputStreams = new FileInputStream[inputCount]; @@ -103,29 +188,12 @@ public final class UsbMidiDevice implements Closeable { mEventSchedulers = new MidiEventScheduler[outputCount]; for (int i = 0; i < outputCount; i++) { mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); - mEventSchedulers[i] = new MidiEventScheduler(); - } - } - private boolean register(Context context, Bundle properties) { - MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); - if (midiManager == null) { - Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); - return false; + MidiEventScheduler scheduler = new MidiEventScheduler(); + mEventSchedulers[i] = scheduler; + mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); } - int inputCount = mInputStreams.length; - int outputCount = mOutputStreams.length; - MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; - for (int port = 0; port < inputCount; port++) { - inputPortReceivers[port] = mEventSchedulers[port].getReceiver(); - } - - mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, - null, null, properties, MidiDeviceInfo.TYPE_USB, null); - if (mServer == null) { - return false; - } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); // Create input thread which will read from all input ports @@ -134,24 +202,32 @@ public final class UsbMidiDevice implements Closeable { public void run() { byte[] buffer = new byte[BUFFER_SIZE]; try { - boolean done = false; - while (!done) { - // look for a readable FileDescriptor - for (int index = 0; index < mPollFDs.length; index++) { - StructPollfd pfd = mPollFDs[index]; - if ((pfd.revents & OsConstants.POLLIN) != 0) { - // clear readable flag - pfd.revents = 0; - - int count = mInputStreams[index].read(buffer); - outputReceivers[index].send(buffer, 0, count); - } else if ((pfd.revents & (OsConstants.POLLERR - | OsConstants.POLLHUP)) != 0) { - done = true; + while (true) { + synchronized (mLock) { + if (!mIsOpen) break; + + // look for a readable FileDescriptor + for (int index = 0; index < mPollFDs.length; index++) { + StructPollfd pfd = mPollFDs[index]; + if ((pfd.revents & (OsConstants.POLLERR + | OsConstants.POLLHUP)) != 0) { + break; + } else if ((pfd.revents & OsConstants.POLLIN) != 0) { + // clear readable flag + pfd.revents = 0; + + if (index == mInputStreams.length - 1) { + // last file descriptor is used only for unblocking Os.poll() + break; + } + + int count = mInputStreams[index].read(buffer); + outputReceivers[index].send(buffer, 0, count); + } } } - // wait until we have a readable port + // wait until we have a readable port or we are signalled to close Os.poll(mPollFDs, -1 /* infinite timeout */); } } catch (IOException e) { @@ -195,29 +271,64 @@ public final class UsbMidiDevice implements Closeable { }.start(); } + mIsOpen = true; + return true; + } + + private boolean register(Context context, Bundle properties) { + MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); + if (midiManager == null) { + Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); + return false; + } + + mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, + null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); + if (mServer == null) { + return false; + } + return true; } @Override public void close() throws IOException { - for (int i = 0; i < mEventSchedulers.length; i++) { - mEventSchedulers[i].close(); + synchronized (mLock) { + if (mIsOpen) { + closeLocked(); + } } if (mServer != null) { - mServer.close(); + IoUtils.closeQuietly(mServer); + } + } + + private void closeLocked() { + for (int i = 0; i < mEventSchedulers.length; i++) { + mInputPortReceivers[i].setReceiver(null); + mEventSchedulers[i].close(); } + mEventSchedulers = null; for (int i = 0; i < mInputStreams.length; i++) { - mInputStreams[i].close(); + IoUtils.closeQuietly(mInputStreams[i]); } + mInputStreams = null; + for (int i = 0; i < mOutputStreams.length; i++) { - mOutputStreams[i].close(); + IoUtils.closeQuietly(mOutputStreams[i]); } + mOutputStreams = null; + + // nativeClose will close the file descriptors and signal the input thread to exit nativeClose(mFileDescriptors); + mFileDescriptors = null; + + mIsOpen = false; } private static native int nativeGetSubdeviceCount(int card, int device); - private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); - private static native void nativeClose(FileDescriptor[] fileDescriptors); + private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); + private native void nativeClose(FileDescriptor[] fileDescriptors); } |