Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 11fd96d6 authored by Mike Lockwood's avatar Mike Lockwood
Browse files

MidiManager: Virtual MIDI devices are now implemented as Services

To implement a virtual MIDI device, include a subclass of MidiDeviceService in
your application.  This service is identified by an intent filter and meta-data
in the application's manifest to allow the MIDI manager to register the virtual device
without actually running the application. Instead, the application's MidiDeviceService
subclass is started on demand when MIDI manager clients want to open the device.

Here is an example of how the MidiDeviceService might be described in the application manifest:

    <service android:name="VirtualDeviceService">
        <intent-filter>
            <action android:name="android.media.midi.MidiDeviceService" />
        </intent-filter>
        <meta-data android:name="android.media.midi.MidiDeviceService"
            android:resource="@xml/device_info" />
    </service>

and the device_info.xml meta-data:

<devices>
    <device manufacturer="Sample Manufacturer" model="Sample Model" private="false">
        <input-port name="my input port" />
        <output-port name="my output port" />
    </device>
</devices>

(note that the <input-port> and <output-port> names are not currently used, but support for these
will be added in a subsequent change)

Client's of the virtual device will bind directly to the hosting application's MidiDeviceService subclass.
To support this, MidiManager.openDevice() now returns the MidiDevice asynchronously via a callback.

This change also adds a utility class called MidiDispatcher, which is a MidiReceiver
that dispatches all data it receives to a list of other MidiReceivers.
We now use this internally in MidiInputPort and MidiDeviceServer, but developers
may use it for other purposes as well.

Change-Id: Ic3009f06d56f3d5edbd87de3f0c330b51a1c217d
parent 8a9588ed
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -333,8 +333,8 @@ LOCAL_SRC_FILES += \
	media/java/android/media/IRingtonePlayer.aidl \
	media/java/android/media/IVolumeController.aidl \
	media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
	media/java/android/media/midi/IMidiDeviceListener.aidl \
	media/java/android/media/midi/IMidiDeviceServer.aidl \
	media/java/android/media/midi/IMidiListener.aidl \
	media/java/android/media/midi/IMidiManager.aidl \
	media/java/android/media/projection/IMediaProjection.aidl \
	media/java/android/media/projection/IMediaProjectionCallback.aidl \
+1 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.media.midi;
import android.media.midi.MidiDeviceInfo;

/** @hide */
oneway interface IMidiListener
oneway interface IMidiDeviceListener
{
    void onDeviceAdded(in MidiDeviceInfo device);
    void onDeviceRemoved(in MidiDeviceInfo device);
+12 −6
Original line number Diff line number Diff line
@@ -16,8 +16,8 @@

package android.media.midi;

import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
import android.media.midi.IMidiListener;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,14 +28,20 @@ interface IMidiManager
    MidiDeviceInfo[] getDeviceList();

    // for device creation & removal notifications
    void registerListener(IBinder token, in IMidiListener listener);
    void unregisterListener(IBinder token, in IMidiListener listener);
    void registerListener(IBinder token, in IMidiDeviceListener listener);
    void unregisterListener(IBinder token, in IMidiDeviceListener listener);

    // for communicating with MIDI devices
    // for opening built-in MIDI devices
    IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);

    // for implementing virtual MIDI devices
    // for registering built-in MIDI devices
    MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
            int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
            int numOutputPorts, in Bundle properties, int type);

    // for unregistering built-in MIDI devices
    void unregisterDeviceServer(in IMidiDeviceServer server);

    // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
    // manifest's meta-data
    MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
}
+31 −3
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ public class MidiDeviceInfo implements Parcelable {
    private final int mInputPortCount;
    private final int mOutputPortCount;
    private final Bundle mProperties;
    private final boolean mIsPrivate;

    /**
     * Bundle key for the device's manufacturer name property.
@@ -83,6 +84,8 @@ public class MidiDeviceInfo implements Parcelable {
     * Bundle key for the device's ALSA card number.
     * Only set for USB MIDI devices.
     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
     *
     * @hide
     */
    public static final String PROPERTY_ALSA_CARD = "alsa_card";

@@ -90,20 +93,32 @@ public class MidiDeviceInfo implements Parcelable {
     * Bundle key for the device's ALSA device number.
     * Only set for USB MIDI devices.
     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
     *
     * @hide
     */
    public static final String PROPERTY_ALSA_DEVICE = "alsa_device";

    /**
     * {@link android.content.pm.ServiceInfo} for the service hosting the device implementation.
     * Only set for Virtual MIDI devices.
     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
     *
     * @hide
     */
    public static final String PROPERTY_SERVICE_INFO = "service_info";

    /**
     * MidiDeviceInfo should only be instantiated by MidiService implementation
     * @hide
     */
    public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
            Bundle properties) {
            Bundle properties, boolean isPrivate) {
        mType = type;
        mId = id;
        mInputPortCount = numInputPorts;
        mOutputPortCount = numOutputPorts;
        mProperties = properties;
        mIsPrivate = isPrivate;
    }

    /**
@@ -152,6 +167,16 @@ public class MidiDeviceInfo implements Parcelable {
        return mProperties;
    }

    /**
     * Returns true if the device is private.  Private devices are only visible and accessible
     * to clients with the same UID as the application that is hosting the device.
     *
     * @return true if the device is private
     */
    public boolean isPrivate() {
        return mIsPrivate;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof MidiDeviceInfo) {
@@ -171,7 +196,8 @@ public class MidiDeviceInfo implements Parcelable {
        return ("MidiDeviceInfo[mType=" + mType +
                ",mInputPortCount=" + mInputPortCount +
                ",mOutputPortCount=" + mOutputPortCount +
                ",mProperties=" + mProperties);
                ",mProperties=" + mProperties +
                ",mIsPrivate=" + mIsPrivate);
    }

    public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -182,7 +208,8 @@ public class MidiDeviceInfo implements Parcelable {
            int inputPorts = in.readInt();
            int outputPorts = in.readInt();
            Bundle properties = in.readBundle();
            return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
            boolean isPrivate = (in.readInt() == 1);
            return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, isPrivate);
        }

        public MidiDeviceInfo[] newArray(int size) {
@@ -200,5 +227,6 @@ public class MidiDeviceInfo implements Parcelable {
        parcel.writeInt(mInputPortCount);
        parcel.writeInt(mOutputPortCount);
        parcel.writeBundle(mProperties);
        parcel.writeInt(mIsPrivate ? 1 : 0);
   }
}
+90 −164
Original line number Diff line number Diff line
@@ -16,21 +16,22 @@

package android.media.midi;

import android.os.IBinder;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;

import libcore.io.IoUtils;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This class is used to provide the implemention of MIDI device.
 * Applications may call {@link MidiManager#createDeviceServer}
 * to create an instance of this class to implement a virtual MIDI device.
 * Internal class used for providing an implementation for a MIDI device.
 *
 * CANDIDATE FOR PUBLIC API
 * @hide
 */
public final class MidiDeviceServer implements Closeable {
@@ -40,64 +41,36 @@ public final class MidiDeviceServer implements Closeable {

    // MidiDeviceInfo for the device implemented by this server
    private MidiDeviceInfo mDeviceInfo;
    private int mInputPortCount;
    private int mOutputPortCount;

    // output ports for receiving messages from our clients
    // we can have only one per port number
    private MidiOutputPort[] mInputPortSenders;

    // receivers attached to our input ports
    private ArrayList<MidiReceiver>[] mInputPortReceivers;

    // input ports for sending messages to our clients
    // we can have multiple outputs per port number
    private ArrayList<MidiInputPort>[] mOutputPortReceivers;

    // subclass of MidiInputPort for passing to clients
    // that notifies us when the connection has failed
    private class ServerInputPort extends MidiInputPort {
        ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
            super(pfd, portNumber);
        }
    private final int mInputPortCount;
    private final int mOutputPortCount;

        @Override
        public void onIOException() {
            synchronized (mOutputPortReceivers) {
                mOutputPortReceivers[getPortNumber()].clear();
            }
        }
    }
    // MidiReceivers for receiving data on our input ports
    private final MidiReceiver[] mInputPortReceivers;

    // subclass of MidiOutputPort for passing to clients
    // that notifies us when the connection has failed
    private class ServerOutputPort extends MidiOutputPort {
        ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
            super(pfd, portNumber);
        }
    // MidiDispatchers for sending data on our output ports
    private MidiDispatcher[] mOutputPortDispatchers;

        @Override
        public void onIOException() {
            synchronized (mInputPortSenders) {
                mInputPortSenders[getPortNumber()] = null;
            }
        }
    }
    // MidiOutputPorts for clients connected to our input ports
    private final MidiOutputPort[] mInputPortOutputPorts;

    // Binder interface stub for receiving connection requests from clients
    private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {

        @Override
        public ParcelFileDescriptor openInputPort(int portNumber) {
            if (mDeviceInfo.isPrivate()) {
                if (Binder.getCallingUid() != Process.myUid()) {
                    throw new SecurityException("Can't access private device from different UID");
                }
            }

            if (portNumber < 0 || portNumber >= mInputPortCount) {
                Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
                return null;
            }

            ParcelFileDescriptor result = null;

            synchronized (mInputPortSenders) {
                if (mInputPortSenders[portNumber] != null) {
            synchronized (mInputPortOutputPorts) {
                if (mInputPortOutputPorts[portNumber] != null) {
                    Log.d(TAG, "port " + portNumber + " already open");
                    return null;
                }
@@ -105,47 +78,86 @@ public final class MidiDeviceServer implements Closeable {
                try {
                    ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
                                                        OsConstants.SOCK_SEQPACKET);
                    MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
                    mInputPortSenders[portNumber] = newOutputPort;
                    result =  pair[1];
                    final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
                    mInputPortOutputPorts[portNumber] = outputPort;
                    final int portNumberF = portNumber;
                    final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber];

                    ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
                    synchronized (receivers) {
                        for (int i = 0; i < receivers.size(); i++) {
                            newOutputPort.connect(receivers.get(i));
                    outputPort.connect(new MidiReceiver() {
                        @Override
                        public void post(byte[] msg, int offset, int count, long timestamp)
                                throws IOException {
                            try {
                                inputPortReceviver.post(msg, offset, count, timestamp);
                            } catch (IOException e) {
                                IoUtils.closeQuietly(mInputPortOutputPorts[portNumberF]);
                                mInputPortOutputPorts[portNumberF] = null;
                                // FIXME also flush the receiver
                            }
                        }
                    });

                    return pair[1];
                } catch (IOException e) {
                    Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
                    return null;
                }
            }

            return result;
        }

        @Override
        public ParcelFileDescriptor openOutputPort(int portNumber) {
            if (mDeviceInfo.isPrivate()) {
                if (Binder.getCallingUid() != Process.myUid()) {
                    throw new SecurityException("Can't access private device from different UID");
                }
            }

            if (portNumber < 0 || portNumber >= mOutputPortCount) {
                Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
                return null;
            }
            synchronized (mOutputPortReceivers) {

            try {
                ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
                                                    OsConstants.SOCK_SEQPACKET);
                    mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
                final MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
                final MidiSender sender = mOutputPortDispatchers[portNumber].getSender();
                sender.connect(new MidiReceiver() {
                        @Override
                        public void post(byte[] msg, int offset, int count, long timestamp)
                                throws IOException {
                            try {
                                inputPort.post(msg, offset, count, timestamp);
                            } catch (IOException e) {
                                IoUtils.closeQuietly(inputPort);
                                sender.disconnect(this);
                                // FIXME also flush the receiver?
                            }
                        }
                    });

                return pair[1];
            } catch (IOException e) {
                Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
                return null;
            }
        }
        }
    };

    /* package */ MidiDeviceServer(IMidiManager midiManager) {
    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
            int numOutputPorts) {
        mMidiManager = midiManager;
        mInputPortReceivers = inputPortReceivers;
        mInputPortCount = inputPortReceivers.length;
        mOutputPortCount = numOutputPorts;

        mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];

        mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
        for (int i = 0; i < numOutputPorts; i++) {
            mOutputPortDispatchers[i] = new MidiDispatcher();
        }
    }

    /* package */ IMidiDeviceServer getBinderInterface() {
@@ -157,19 +169,6 @@ public final class MidiDeviceServer implements Closeable {
            throw new IllegalStateException("setDeviceInfo should only be called once");
        }
        mDeviceInfo = deviceInfo;
        mInputPortCount = deviceInfo.getInputPortCount();
        mOutputPortCount = deviceInfo.getOutputPortCount();
        mInputPortSenders = new MidiOutputPort[mInputPortCount];

        mInputPortReceivers = new ArrayList[mInputPortCount];
        for (int i = 0; i < mInputPortCount; i++) {
            mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
        }

        mOutputPortReceivers = new ArrayList[mOutputPortCount];
        for (int i = 0; i < mOutputPortCount; i++) {
            mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
        }
    }

    @Override
@@ -183,86 +182,13 @@ public final class MidiDeviceServer implements Closeable {
    }

    /**
     * Returns a {@link MidiDeviceInfo} object, which describes this device.
     *
     * @return the {@link MidiDeviceInfo} object
     */
    public MidiDeviceInfo getInfo() {
        return mDeviceInfo;
    }

    /**
     * Called to open a {@link MidiSender} to allow receiving MIDI messages
     * on the device's input port for the specified port number.
     *
     * @param portNumber the number of the input port
     * @return the {@link MidiSender}
     * Returns an array of {@link MidiReceiver} for the device's output ports.
     * Clients can use these receivers to send data out the device's output ports.
     * @return array of MidiReceivers
     */
    public MidiSender openInputPortSender(int portNumber) {
        if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
            throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
        }
        final int portNumberF = portNumber;
        return new MidiSender() {

            @Override
            public void connect(MidiReceiver receiver) {
                // We always synchronize on mInputPortSenders before receivers if we need to
                // synchronize on both.
                synchronized (mInputPortSenders) {
                    ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
                    synchronized (receivers) {
                        receivers.add(receiver);
                        MidiOutputPort outputPort = mInputPortSenders[portNumberF];
                        if (outputPort != null) {
                            outputPort.connect(receiver);
                        }
                    }
                }
            }

            @Override
            public void disconnect(MidiReceiver receiver) {
                // We always synchronize on mInputPortSenders before receivers if we need to
                // synchronize on both.
                synchronized (mInputPortSenders) {
                    ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
                    synchronized (receivers) {
                        receivers.remove(receiver);
                        MidiOutputPort outputPort = mInputPortSenders[portNumberF];
                        if (outputPort != null) {
                            outputPort.disconnect(receiver);
                        }
                    }
                }
            }
        };
    }

    /**
     * Called to open a {@link MidiReceiver} to allow sending MIDI messages
     * on the virtual device's output port for the specified port number.
     *
     * @param portNumber the number of the output port
     * @return the {@link MidiReceiver}
     */
    public MidiReceiver openOutputPortReceiver(int portNumber) {
        if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
            throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
        }
        final int portNumberF = portNumber;
        return new MidiReceiver() {

            @Override
            public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
                ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
                synchronized (receivers) {
                    for (int i = 0; i < receivers.size(); i++) {
                        // FIXME catch errors and remove dead ones
                        receivers.get(i).post(msg, offset, count, timestamp);
                    }
                }
            }
        };
    public MidiReceiver[] getOutputPortReceivers() {
        MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
        System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
        return receivers;
    }
}
Loading