summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--services/core/jni/com_android_server_UsbMidiDevice.cpp30
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java229
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);
}