diff options
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; |