summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/midi/MidiDispatcher.java41
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java50
-rw-r--r--media/java/android/media/midi/MidiOutputPort.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java30
4 files changed, 100 insertions, 23 deletions
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 1a3c37c79cd2..c16628a9c692 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -26,11 +26,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Utility class for dispatching MIDI data to a list of {@link android.media.midi.MidiReceiver}s.
* This class subclasses {@link android.media.midi.MidiReceiver} and dispatches any data it receives
* to its receiver list. Any receivers that throw an exception upon receiving data will
- * be automatically removed from the receiver list, but no IOException will be returned
- * from the dispatcher's {@link android.media.midi.MidiReceiver#onSend} in that case.
+ * be automatically removed from the receiver list. If a MidiReceiverFailureHandler has been
+ * provided to the MidiDispatcher, it will be notified about the failure, but the exception
+ * itself will be swallowed.
*/
public final class MidiDispatcher extends MidiReceiver {
+ // MidiDispatcher's client and MidiReceiver's owner can be different
+ // classes (e.g. MidiDeviceService is a client, but MidiDeviceServer is
+ // the owner), and errors occuring during sending need to be reported
+ // to the owner rather than to the sender.
+ //
+ // Note that the callbacks will be called on the sender's thread.
+ public interface MidiReceiverFailureHandler {
+ void onReceiverFailure(MidiReceiver receiver, IOException failure);
+ }
+
+ private final MidiReceiverFailureHandler mFailureHandler;
private final CopyOnWriteArrayList<MidiReceiver> mReceivers
= new CopyOnWriteArrayList<MidiReceiver>();
@@ -46,6 +58,14 @@ public final class MidiDispatcher extends MidiReceiver {
}
};
+ public MidiDispatcher() {
+ this(null);
+ }
+
+ public MidiDispatcher(MidiReceiverFailureHandler failureHandler) {
+ mFailureHandler = failureHandler;
+ }
+
/**
* Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains.
* @return the number of receivers
@@ -70,8 +90,13 @@ public final class MidiDispatcher extends MidiReceiver {
try {
receiver.send(msg, offset, count, timestamp);
} catch (IOException e) {
- // if the receiver fails we remove the receiver but do not propagate the exception
+ // If the receiver fails we remove the receiver but do not propagate the exception.
+ // Note that this may also happen if the client code stalls, and thus underlying
+ // MidiInputPort.onSend has raised IOException for EAGAIN / EWOULDBLOCK error.
mReceivers.remove(receiver);
+ if (mFailureHandler != null) {
+ mFailureHandler.onReceiverFailure(receiver, e);
+ }
}
}
}
@@ -79,7 +104,15 @@ public final class MidiDispatcher extends MidiReceiver {
@Override
public void onFlush() throws IOException {
for (MidiReceiver receiver : mReceivers) {
- receiver.flush();
+ try {
+ receiver.flush();
+ } catch (IOException e) {
+ // This is just a special case of 'send' thus handle in the same way.
+ mReceivers.remove(receiver);
+ if (mFailureHandler != null) {
+ mFailureHandler.onReceiverFailure(receiver, e);
+ }
+ }
}
}
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index f0abf711ba16..4c49f677c031 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -73,6 +73,10 @@ public final class MidiDeviceServer implements Closeable {
private final Callback mCallback;
+ private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
+ private final HashMap<MidiInputPort, PortClient> mInputPortClients =
+ new HashMap<MidiInputPort, PortClient>();
+
public interface Callback {
/**
* Called to notify when an our device status has changed
@@ -102,6 +106,10 @@ public final class MidiDeviceServer implements Closeable {
abstract void close();
+ MidiInputPort getInputPort() {
+ return null;
+ }
+
@Override
public void binderDied() {
close();
@@ -152,9 +160,12 @@ public final class MidiDeviceServer implements Closeable {
mInputPorts.remove(mInputPort);
IoUtils.closeQuietly(mInputPort);
}
- }
- private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
+ @Override
+ MidiInputPort getInputPort() {
+ return mInputPort;
+ }
+ }
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@@ -215,6 +226,12 @@ public final class MidiDeviceServer implements Closeable {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
+ // Undo the default blocking-mode of the server-side socket for
+ // physical devices to avoid stalling the Java device handler if
+ // client app code gets stuck inside 'onSend' handler.
+ if (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL) {
+ IoUtils.setBlocking(pair[0].getFileDescriptor(), false);
+ }
MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
synchronized (dispatcher) {
dispatcher.getSender().connect(inputPort);
@@ -228,6 +245,9 @@ public final class MidiDeviceServer implements Closeable {
synchronized (mPortClients) {
mPortClients.put(token, client);
}
+ synchronized (mInputPortClients) {
+ mInputPortClients.put(inputPort, client);
+ }
return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
@@ -237,12 +257,19 @@ public final class MidiDeviceServer implements Closeable {
@Override
public void closePort(IBinder token) {
+ MidiInputPort inputPort = null;
synchronized (mPortClients) {
PortClient client = mPortClients.remove(token);
if (client != null) {
+ inputPort = client.getInputPort();
client.close();
}
}
+ if (inputPort != null) {
+ synchronized (mInputPortClients) {
+ mInputPortClients.remove(inputPort);
+ }
+ }
}
@Override
@@ -270,6 +297,9 @@ public final class MidiDeviceServer implements Closeable {
synchronized (mPortClients) {
mPortClients.put(token, client);
}
+ synchronized (mInputPortClients) {
+ mInputPortClients.put(inputPort, client);
+ }
return Process.myPid(); // for caller to detect same process ID
}
@@ -303,7 +333,7 @@ public final class MidiDeviceServer implements Closeable {
mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
for (int i = 0; i < numOutputPorts; i++) {
- mOutputPortDispatchers[i] = new MidiDispatcher();
+ mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
}
mInputPortOpen = new boolean[mInputPortCount];
@@ -312,6 +342,20 @@ public final class MidiDeviceServer implements Closeable {
mGuard.open("close");
}
+ private final MidiDispatcher.MidiReceiverFailureHandler mInputPortFailureHandler =
+ new MidiDispatcher.MidiReceiverFailureHandler() {
+ public void onReceiverFailure(MidiReceiver receiver, IOException failure) {
+ Log.e(TAG, "MidiInputPort failed to send data", failure);
+ PortClient client = null;
+ synchronized (mInputPortClients) {
+ client = mInputPortClients.remove(receiver);
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+ };
+
// Constructor for MidiDeviceService.onCreate()
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
MidiDeviceInfo deviceInfo, Callback callback) {
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0096995bac28..54c31e34ef16 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -83,7 +83,7 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
}
} catch (IOException e) {
// FIXME report I/O failure?
- Log.e(TAG, "read failed");
+ Log.e(TAG, "read failed", e);
} finally {
IoUtils.closeQuietly(mInputStream);
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 46ce7a0a1aaf..cd19795747f5 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -51,7 +51,7 @@ public final class UsbMidiDevice implements Closeable {
private MidiDeviceServer mServer;
- // event schedulers for each output port
+ // event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512;
@@ -160,9 +160,9 @@ public final class UsbMidiDevice implements Closeable {
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++) {
+ int inputPortCount = subdeviceCount;
+ mInputPortReceivers = new InputReceiverProxy[inputPortCount];
+ for (int port = 0; port < inputPortCount; port++) {
mInputPortReceivers[port] = new InputReceiverProxy();
}
}
@@ -176,14 +176,14 @@ public final class UsbMidiDevice implements Closeable {
}
mFileDescriptors = fileDescriptors;
- int inputCount = fileDescriptors.length;
+ int inputStreamCount = fileDescriptors.length;
// last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
// in our input thread
- int outputCount = fileDescriptors.length - 1;
+ int outputStreamCount = fileDescriptors.length - 1;
- mPollFDs = new StructPollfd[inputCount];
- mInputStreams = new FileInputStream[inputCount];
- for (int i = 0; i < inputCount; i++) {
+ mPollFDs = new StructPollfd[inputStreamCount];
+ mInputStreams = new FileInputStream[inputStreamCount];
+ for (int i = 0; i < inputStreamCount; i++) {
FileDescriptor fd = fileDescriptors[i];
StructPollfd pollfd = new StructPollfd();
pollfd.fd = fd;
@@ -192,9 +192,9 @@ public final class UsbMidiDevice implements Closeable {
mInputStreams[i] = new FileInputStream(fd);
}
- mOutputStreams = new FileOutputStream[outputCount];
- mEventSchedulers = new MidiEventScheduler[outputCount];
- for (int i = 0; i < outputCount; i++) {
+ mOutputStreams = new FileOutputStream[outputStreamCount];
+ mEventSchedulers = new MidiEventScheduler[outputStreamCount];
+ for (int i = 0; i < outputStreamCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
MidiEventScheduler scheduler = new MidiEventScheduler();
@@ -204,7 +204,7 @@ public final class UsbMidiDevice implements Closeable {
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // Create input thread which will read from all input ports
+ // Create input thread which will read from all output ports of the physical device
new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
@@ -249,8 +249,8 @@ public final class UsbMidiDevice implements Closeable {
}
}.start();
- // Create output thread for each output port
- for (int port = 0; port < outputCount; port++) {
+ // Create output thread for each input port of the physical device
+ for (int port = 0; port < outputStreamCount; port++) {
final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
final FileOutputStream outputStreamF = mOutputStreams[port];
final int portF = port;