Merge "Squashed commit of the following:"

This commit is contained in:
Glenn Kasten
2016-08-17 18:02:53 +00:00
committed by Gerrit Code Review
8 changed files with 148 additions and 45 deletions

View File

@@ -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. * 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 * 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 * 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 * be automatically removed from the receiver list. If a MidiReceiverFailureHandler has been
* from the dispatcher's {@link android.media.midi.MidiReceiver#onSend} in that case. * provided to the MidiDispatcher, it will be notified about the failure, but the exception
* itself will be swallowed.
*/ */
public final class MidiDispatcher extends MidiReceiver { 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 private final CopyOnWriteArrayList<MidiReceiver> mReceivers
= new CopyOnWriteArrayList<MidiReceiver>(); = 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. * Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains.
* @return the number of receivers * @return the number of receivers
@@ -70,8 +90,13 @@ public final class MidiDispatcher extends MidiReceiver {
try { try {
receiver.send(msg, offset, count, timestamp); receiver.send(msg, offset, count, timestamp);
} catch (IOException e) { } 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); mReceivers.remove(receiver);
if (mFailureHandler != null) {
mFailureHandler.onReceiverFailure(receiver, e);
}
} }
} }
} }
@@ -79,7 +104,15 @@ public final class MidiDispatcher extends MidiReceiver {
@Override @Override
public void onFlush() throws IOException { public void onFlush() throws IOException {
for (MidiReceiver receiver : mReceivers) { for (MidiReceiver receiver : mReceivers) {
try {
receiver.flush(); 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);
}
}
} }
} }
} }

View File

@@ -28,7 +28,8 @@ interface IMidiDeviceServer
void closeDevice(); void closeDevice();
// connects the input port pfd to the specified output port // connects the input port pfd to the specified output port
void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber); // Returns the PID of the called process.
int connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
MidiDeviceInfo getDeviceInfo(); MidiDeviceInfo getDeviceInfo();
void setDeviceInfo(in MidiDeviceInfo deviceInfo); void setDeviceInfo(in MidiDeviceInfo deviceInfo);

View File

@@ -19,6 +19,7 @@ package android.media.midi;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
@@ -41,6 +42,7 @@ public final class MidiDevice implements Closeable {
private final IMidiManager mMidiManager; private final IMidiManager mMidiManager;
private final IBinder mClientToken; private final IBinder mClientToken;
private final IBinder mDeviceToken; private final IBinder mDeviceToken;
private boolean mIsDeviceClosed;
private final CloseGuard mGuard = CloseGuard.get(); private final CloseGuard mGuard = CloseGuard.get();
@@ -122,6 +124,9 @@ public final class MidiDevice implements Closeable {
* or null in case of failure. * or null in case of failure.
*/ */
public MidiInputPort openInputPort(int portNumber) { public MidiInputPort openInputPort(int portNumber) {
if (mIsDeviceClosed) {
return null;
}
try { try {
IBinder token = new Binder(); IBinder token = new Binder();
ParcelFileDescriptor pfd = mDeviceServer.openInputPort(token, portNumber); ParcelFileDescriptor pfd = mDeviceServer.openInputPort(token, portNumber);
@@ -145,6 +150,9 @@ public final class MidiDevice implements Closeable {
* or null in case of failure. * or null in case of failure.
*/ */
public MidiOutputPort openOutputPort(int portNumber) { public MidiOutputPort openOutputPort(int portNumber) {
if (mIsDeviceClosed) {
return null;
}
try { try {
IBinder token = new Binder(); IBinder token = new Binder();
ParcelFileDescriptor pfd = mDeviceServer.openOutputPort(token, portNumber); ParcelFileDescriptor pfd = mDeviceServer.openOutputPort(token, portNumber);
@@ -174,6 +182,9 @@ public final class MidiDevice implements Closeable {
if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) { if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
throw new IllegalArgumentException("outputPortNumber out of range"); throw new IllegalArgumentException("outputPortNumber out of range");
} }
if (mIsDeviceClosed) {
return null;
}
ParcelFileDescriptor pfd = inputPort.claimFileDescriptor(); ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
if (pfd == null) { if (pfd == null) {
@@ -181,9 +192,16 @@ public final class MidiDevice implements Closeable {
} }
try { try {
IBinder token = new Binder(); IBinder token = new Binder();
mDeviceServer.connectPorts(token, pfd, outputPortNumber); int calleePid = mDeviceServer.connectPorts(token, pfd, outputPortNumber);
// If the service is a different Process then it will duplicate the pfd
// and we can safely close this one.
// But if the service is in the same Process then closing the pfd will
// kill the connection. So don't do that.
if (calleePid != Process.myPid()) {
// close our copy of the file descriptor // close our copy of the file descriptor
IoUtils.closeQuietly(pfd); IoUtils.closeQuietly(pfd);
}
return new MidiConnection(token, inputPort); return new MidiConnection(token, inputPort);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "RemoteException in connectPorts"); Log.e(TAG, "RemoteException in connectPorts");
@@ -194,7 +212,9 @@ public final class MidiDevice implements Closeable {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
synchronized (mGuard) { synchronized (mGuard) {
if (!mIsDeviceClosed) {
mGuard.close(); mGuard.close();
mIsDeviceClosed = true;
try { try {
mMidiManager.closeDevice(mClientToken, mDeviceToken); mMidiManager.closeDevice(mClientToken, mDeviceToken);
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -202,6 +222,7 @@ public final class MidiDevice implements Closeable {
} }
} }
} }
}
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {

View File

@@ -73,6 +73,10 @@ public final class MidiDeviceServer implements Closeable {
private final Callback mCallback; 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 { public interface Callback {
/** /**
* Called to notify when an our device status has changed * Called to notify when an our device status has changed
@@ -102,6 +106,10 @@ public final class MidiDeviceServer implements Closeable {
abstract void close(); abstract void close();
MidiInputPort getInputPort() {
return null;
}
@Override @Override
public void binderDied() { public void binderDied() {
close(); close();
@@ -152,9 +160,12 @@ public final class MidiDeviceServer implements Closeable {
mInputPorts.remove(mInputPort); mInputPorts.remove(mInputPort);
IoUtils.closeQuietly(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 // Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@@ -215,6 +226,12 @@ public final class MidiDeviceServer implements Closeable {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET); OsConstants.SOCK_SEQPACKET);
MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber); 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]; MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
synchronized (dispatcher) { synchronized (dispatcher) {
dispatcher.getSender().connect(inputPort); dispatcher.getSender().connect(inputPort);
@@ -228,6 +245,9 @@ public final class MidiDeviceServer implements Closeable {
synchronized (mPortClients) { synchronized (mPortClients) {
mPortClients.put(token, client); mPortClients.put(token, client);
} }
synchronized (mInputPortClients) {
mInputPortClients.put(inputPort, client);
}
return pair[1]; return pair[1];
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
@@ -237,12 +257,19 @@ public final class MidiDeviceServer implements Closeable {
@Override @Override
public void closePort(IBinder token) { public void closePort(IBinder token) {
MidiInputPort inputPort = null;
synchronized (mPortClients) { synchronized (mPortClients) {
PortClient client = mPortClients.remove(token); PortClient client = mPortClients.remove(token);
if (client != null) { if (client != null) {
inputPort = client.getInputPort();
client.close(); client.close();
} }
} }
if (inputPort != null) {
synchronized (mInputPortClients) {
mInputPortClients.remove(inputPort);
}
}
} }
@Override @Override
@@ -254,7 +281,7 @@ public final class MidiDeviceServer implements Closeable {
} }
@Override @Override
public void connectPorts(IBinder token, ParcelFileDescriptor pfd, public int connectPorts(IBinder token, ParcelFileDescriptor pfd,
int outputPortNumber) { int outputPortNumber) {
MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber); MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber]; MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber];
@@ -270,6 +297,10 @@ public final class MidiDeviceServer implements Closeable {
synchronized (mPortClients) { synchronized (mPortClients) {
mPortClients.put(token, client); mPortClients.put(token, client);
} }
synchronized (mInputPortClients) {
mInputPortClients.put(inputPort, client);
}
return Process.myPid(); // for caller to detect same process ID
} }
@Override @Override
@@ -302,7 +333,7 @@ public final class MidiDeviceServer implements Closeable {
mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
for (int i = 0; i < numOutputPorts; i++) { for (int i = 0; i < numOutputPorts; i++) {
mOutputPortDispatchers[i] = new MidiDispatcher(); mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
} }
mInputPortOpen = new boolean[mInputPortCount]; mInputPortOpen = new boolean[mInputPortCount];
@@ -311,6 +342,20 @@ public final class MidiDeviceServer implements Closeable {
mGuard.open("close"); 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() // Constructor for MidiDeviceService.onCreate()
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
MidiDeviceInfo deviceInfo, Callback callback) { MidiDeviceInfo deviceInfo, Callback callback) {

View File

@@ -83,7 +83,7 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
} }
} catch (IOException e) { } catch (IOException e) {
// FIXME report I/O failure? // FIXME report I/O failure?
Log.e(TAG, "read failed"); Log.e(TAG, "read failed", e);
} finally { } finally {
IoUtils.closeQuietly(mInputStream); IoUtils.closeQuietly(mInputStream);
} }

View File

@@ -1,12 +1,13 @@
<html> <html>
<body> <body>
<p>Android MIDI User Guide</p>
<p>
Provides classes for sending and receiving messages using the standard MIDI
event protocol over USB, Bluetooth LE, and virtual (inter-app) transports.
</p>
<h1 id=overview>Overview</h1> <h1 id=overview>Overview</h1>
<p>This document describes how to use the Android MIDI API in Java.</p>
<p>The Android MIDI package allows users to:</p> <p>The Android MIDI package allows users to:</p>
<ul> <ul>

View File

@@ -126,8 +126,8 @@ public class MidiService extends IMidiManager.Stub {
// This client's PID // This client's PID
private final int mPid; private final int mPid;
// List of all receivers for this client // List of all receivers for this client
private final ArrayList<IMidiDeviceListener> mListeners private final HashMap<IBinder, IMidiDeviceListener> mListeners
= new ArrayList<IMidiDeviceListener>(); = new HashMap<IBinder, IMidiDeviceListener>();
// List of all device connections for this client // List of all device connections for this client
private final HashMap<IBinder, DeviceConnection> mDeviceConnections private final HashMap<IBinder, DeviceConnection> mDeviceConnections
= new HashMap<IBinder, DeviceConnection>(); = new HashMap<IBinder, DeviceConnection>();
@@ -143,11 +143,13 @@ public class MidiService extends IMidiManager.Stub {
} }
public void addListener(IMidiDeviceListener listener) { public void addListener(IMidiDeviceListener listener) {
mListeners.add(listener); // Use asBinder() so that we can match it in removeListener().
// The listener proxy objects themselves do not match.
mListeners.put(listener.asBinder(), listener);
} }
public void removeListener(IMidiDeviceListener listener) { public void removeListener(IMidiDeviceListener listener) {
mListeners.remove(listener); mListeners.remove(listener.asBinder());
if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
close(); close();
} }
@@ -184,7 +186,7 @@ public class MidiService extends IMidiManager.Stub {
MidiDeviceInfo deviceInfo = device.getDeviceInfo(); MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try { try {
for (IMidiDeviceListener listener : mListeners) { for (IMidiDeviceListener listener : mListeners.values()) {
listener.onDeviceAdded(deviceInfo); listener.onDeviceAdded(deviceInfo);
} }
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -198,7 +200,7 @@ public class MidiService extends IMidiManager.Stub {
MidiDeviceInfo deviceInfo = device.getDeviceInfo(); MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try { try {
for (IMidiDeviceListener listener : mListeners) { for (IMidiDeviceListener listener : mListeners.values()) {
listener.onDeviceRemoved(deviceInfo); listener.onDeviceRemoved(deviceInfo);
} }
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -211,7 +213,7 @@ public class MidiService extends IMidiManager.Stub {
if (!device.isUidAllowed(mUid)) return; if (!device.isUidAllowed(mUid)) return;
try { try {
for (IMidiDeviceListener listener : mListeners) { for (IMidiDeviceListener listener : mListeners.values()) {
listener.onDeviceStatusChanged(status); listener.onDeviceStatusChanged(status);
} }
} catch (RemoteException e) { } catch (RemoteException e) {

View File

@@ -51,7 +51,7 @@ public final class UsbMidiDevice implements Closeable {
private MidiDeviceServer mServer; private MidiDeviceServer mServer;
// event schedulers for each output port // event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers; private MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512; private static final int BUFFER_SIZE = 512;
@@ -160,9 +160,9 @@ public final class UsbMidiDevice implements Closeable {
mSubdeviceCount = subdeviceCount; mSubdeviceCount = subdeviceCount;
// FIXME - support devices with different number of input and output ports // FIXME - support devices with different number of input and output ports
int inputCount = subdeviceCount; int inputPortCount = subdeviceCount;
mInputPortReceivers = new InputReceiverProxy[inputCount]; mInputPortReceivers = new InputReceiverProxy[inputPortCount];
for (int port = 0; port < inputCount; port++) { for (int port = 0; port < inputPortCount; port++) {
mInputPortReceivers[port] = new InputReceiverProxy(); mInputPortReceivers[port] = new InputReceiverProxy();
} }
} }
@@ -176,14 +176,14 @@ public final class UsbMidiDevice implements Closeable {
} }
mFileDescriptors = fileDescriptors; mFileDescriptors = fileDescriptors;
int inputCount = fileDescriptors.length; int inputStreamCount = fileDescriptors.length;
// last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
// in our input thread // in our input thread
int outputCount = fileDescriptors.length - 1; int outputStreamCount = fileDescriptors.length - 1;
mPollFDs = new StructPollfd[inputCount]; mPollFDs = new StructPollfd[inputStreamCount];
mInputStreams = new FileInputStream[inputCount]; mInputStreams = new FileInputStream[inputStreamCount];
for (int i = 0; i < inputCount; i++) { for (int i = 0; i < inputStreamCount; i++) {
FileDescriptor fd = fileDescriptors[i]; FileDescriptor fd = fileDescriptors[i];
StructPollfd pollfd = new StructPollfd(); StructPollfd pollfd = new StructPollfd();
pollfd.fd = fd; pollfd.fd = fd;
@@ -192,9 +192,9 @@ public final class UsbMidiDevice implements Closeable {
mInputStreams[i] = new FileInputStream(fd); mInputStreams[i] = new FileInputStream(fd);
} }
mOutputStreams = new FileOutputStream[outputCount]; mOutputStreams = new FileOutputStream[outputStreamCount];
mEventSchedulers = new MidiEventScheduler[outputCount]; mEventSchedulers = new MidiEventScheduler[outputStreamCount];
for (int i = 0; i < outputCount; i++) { for (int i = 0; i < outputStreamCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
MidiEventScheduler scheduler = new MidiEventScheduler(); MidiEventScheduler scheduler = new MidiEventScheduler();
@@ -204,7 +204,7 @@ public final class UsbMidiDevice implements Closeable {
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 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") { new Thread("UsbMidiDevice input thread") {
@Override @Override
public void run() { public void run() {
@@ -249,8 +249,8 @@ public final class UsbMidiDevice implements Closeable {
} }
}.start(); }.start();
// Create output thread for each output port // Create output thread for each input port of the physical device
for (int port = 0; port < outputCount; port++) { for (int port = 0; port < outputStreamCount; port++) {
final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
final FileOutputStream outputStreamF = mOutputStreams[port]; final FileOutputStream outputStreamF = mOutputStreams[port];
final int portF = port; final int portF = port;