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

Commit c623ec97 authored by Mike Lockwood's avatar Mike Lockwood Committed by Android (Google) Code Review
Browse files

Merge "MidiManager: Virtual MIDI devices are now implemented as Services"

parents ef7aa4fc 11fd96d6
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