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

Commit 10024b3d authored by Mike Lockwood's avatar Mike Lockwood
Browse files

MidiManager updates:

MIDI ports are now implemented as file descriptors directly between the sender
and receiver, so the MidiService is no longer in the message path.

To facilitate the above, each port has its own file descriptor, rather than multiplexing
all ports on a device through a single socket.

Added a new class MidiDeviceServer, which is used by implementors of MIDI devices.
This replaces the MidiVirtualDevice class (which only was included in changes that were reviewed but never submitted).

The USB MIDI implementation has moved from the MIDI service to the USB service.
The USB MIDI implementation uses MidiDeviceServer as its interface, so we now have a common
interface for all MIDI device implementations.

Change-Id: I8effd1583f344beb6c940c3a24dbf20b477a6436
parent 34b064a1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -174,6 +174,7 @@ LOCAL_SRC_FILES += \
	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
	core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
	core/java/android/hardware/usb/IUsbManager.aidl \
	core/java/android/midi/IMidiDeviceServer.aidl \
	core/java/android/midi/IMidiListener.aidl \
	core/java/android/midi/IMidiManager.aidl \
	core/java/android/net/IConnectivityManager.aidl \
+10 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014, The Android Open Source Project
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,4 +16,11 @@

package android.midi;

parcelable MidiDevice;
import android.os.ParcelFileDescriptor;

/** @hide */
interface IMidiDeviceServer
{
    ParcelFileDescriptor openInputPort(int portNumber);
    ParcelFileDescriptor openOutputPort(int portNumber);
}
+5 −11
Original line number Diff line number Diff line
@@ -16,13 +16,11 @@

package android.midi;

import android.hardware.usb.UsbDevice;
import android.midi.IMidiDeviceServer;
import android.midi.IMidiListener;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;

/** @hide */
interface IMidiManager
@@ -34,14 +32,10 @@ interface IMidiManager
    void unregisterListener(IBinder token, in IMidiListener listener);

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

    // for implementing virtual MIDI devices
    MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
            in Bundle properties);
    void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device);

    // for use by UsbAudioManager
    void alsaDeviceAdded(int card, int device, in UsbDevice usbDevice);
    void alsaDeviceRemoved(in UsbDevice usbDevice);
    MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
            int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
    void unregisterDeviceServer(in IMidiDeviceServer server);
}
+30 −278
Original line number Diff line number Diff line
@@ -16,9 +16,8 @@

package android.midi;

import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import java.io.FileDescriptor;
@@ -30,114 +29,31 @@ import java.util.ArrayList;
/**
 * This class is used for sending and receiving data to and from an MIDI device
 * Instances of this class are created by {@link MidiManager#openDevice}.
 * This class can also be used to provide the implementation for a virtual device.
 *
 * This class implements Parcelable so it can be returned from MidiService when creating
 * virtual MIDI devices.
 *
 * @hide
 */
public final class MidiDevice implements Parcelable {
public final class MidiDevice {
    private static final String TAG = "MidiDevice";

    private final MidiDeviceInfo mDeviceInfo;
    private ParcelFileDescriptor mParcelFileDescriptor;
    private FileInputStream mInputStream;
    private FileOutputStream mOutputStream;

    // lazily populated lists of ports
    private final MidiInputPort[] mInputPorts;
    private final MidiOutputPort[] mOutputPorts;

    // array of receiver lists, indexed by port number
    private final ArrayList<MidiReceiver>[] mReceivers;

    private int mReceiverCount; // total number of receivers for all ports

    /**
     * Minimum size of packed message as sent through our ParcelFileDescriptor
     * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
     * @hide
     */
    public static final int MIN_PACKED_MESSAGE_SIZE = 10;
    private final IMidiDeviceServer mServer;

   /**
     * Maximum size of packed message as sent through our ParcelFileDescriptor
     * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
     * MidiDevice should only be instantiated by MidiManager
     * @hide
     */
    public static final int MAX_PACKED_MESSAGE_SIZE = 12;

    // This thread reads MIDI events from a socket and distributes them to the list of
    // MidiReceivers attached to this device.
    private final Thread mThread = new Thread() {
        @Override
        public void run() {
            byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
            ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();

            try {
                while (true) {
                    // read next event
                    int count = mInputStream.read(buffer);
                    if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
                        Log.e(TAG, "Number of bytes read out of range: " + count);
                        break;
                    }

                    int offset = getMessageOffset(buffer, count);
                    int size = getMessageSize(buffer, count);
                    long timestamp = getMessageTimeStamp(buffer, count);
                    int port = getMessagePortNumber(buffer, count);

                    synchronized (mReceivers) {
                        ArrayList<MidiReceiver> receivers = mReceivers[port];
                        if (receivers != null) {
                            for (int i = 0; i < receivers.size(); i++) {
                                MidiReceiver receiver = receivers.get(i);
                                try {
                                    receivers.get(i).onPost(buffer, offset, size, timestamp);
                                } catch (IOException e) {
                                    Log.e(TAG, "post failed");
                                    deadReceivers.add(receiver);
                                }
                            }
                            // remove any receivers that failed
                            if (deadReceivers.size() > 0) {
                                for (MidiReceiver receiver: deadReceivers) {
                                    receivers.remove(receiver);
                                    mReceiverCount--;
                                }
                                deadReceivers.clear();
                            }
                            if (receivers.size() == 0) {
                                mReceivers[port] = null;
                            }
                            // exit if we have no receivers left
                            if (mReceiverCount == 0) {
                                break;
                            }
                        }
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "read failed");
            }
    public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
        mDeviceInfo = deviceInfo;
        mServer = server;
    }
    };

    /**
     * MidiDevice should only be instantiated by MidiManager or MidiService
     * @hide
     * Returns a {@link MidiDeviceInfo} object, which describes this device.
     *
     * @return the {@link MidiDeviceInfo} object
     */
    public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
        mDeviceInfo = deviceInfo;
        mParcelFileDescriptor = pfd;
        int inputPorts = deviceInfo.getInputPortCount();
        int outputPorts = deviceInfo.getOutputPortCount();
        mInputPorts = new MidiInputPort[inputPorts];
        mOutputPorts = new MidiOutputPort[outputPorts];
        mReceivers = new ArrayList[outputPorts];
    public MidiDeviceInfo getInfo() {
        return mDeviceInfo;
    }

    /**
@@ -147,14 +63,15 @@ public final class MidiDevice implements Parcelable {
     * @return the {@link MidiInputPort}
     */
    public MidiInputPort openInputPort(int portNumber) {
        if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
            throw new IllegalArgumentException("input port number out of range");
        }
        synchronized (mInputPorts) {
            if (mInputPorts[portNumber] == null) {
                mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber);
        try {
            ParcelFileDescriptor pfd = mServer.openInputPort(portNumber);
            if (pfd == null) {
                return null;
            }
            return mInputPorts[portNumber];
            return new MidiInputPort(pfd, portNumber);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in openInputPort");
            return null;
        }
    }

@@ -165,185 +82,20 @@ public final class MidiDevice implements Parcelable {
     * @return the {@link MidiOutputPort}
     */
    public MidiOutputPort openOutputPort(int portNumber) {
        if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
            throw new IllegalArgumentException("output port number out of range");
        }
        synchronized (mOutputPorts) {
            if (mOutputPorts[portNumber] == null) {
                mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber);
            }
            return mOutputPorts[portNumber];
        }
    }

    /* package */ void connect(MidiReceiver receiver, int portNumber) {
        synchronized (mReceivers) {
            if (mReceivers[portNumber] == null) {
                mReceivers[portNumber] = new  ArrayList<MidiReceiver>();
            }
            mReceivers[portNumber].add(receiver);
            if (mReceiverCount++ == 0) {
                mThread.start();
            }
        }
    }

    /* package */ void disconnect(MidiReceiver receiver, int portNumber) {
        synchronized (mReceivers) {
            ArrayList<MidiReceiver> receivers = mReceivers[portNumber];
            if (receivers != null && receivers.remove(receiver)) {
                mReceiverCount--;
            }
        }
    }

    /* package */ boolean open() {
        FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
        try {
            mInputStream = new FileInputStream(fd);
        } catch (Exception e) {
            Log.e(TAG, "could not create mInputStream", e);
            return false;
        }

        try {
            mOutputStream = new FileOutputStream(fd);
        } catch (Exception e) {
            Log.e(TAG, "could not create mOutputStream", e);
            return false;
        }
        return true;
    }

    /* package */ void close() {
        try {
            if (mInputStream != null) {
                mInputStream.close();
            }
            if (mOutputStream != null) {
                mOutputStream.close();
            ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber);
            if (pfd == null) {
                return null;
            }
            mParcelFileDescriptor.close();
        } catch (IOException e) {
            return new MidiOutputPort(pfd, portNumber);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in openOutputPort");
            return null;
        }
    }

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

    @Override
    public String toString() {
        return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
    }

    public static final Parcelable.Creator<MidiDevice> CREATOR =
        new Parcelable.Creator<MidiDevice>() {
        public MidiDevice createFromParcel(Parcel in) {
            MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null);
            ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null);
            return new MidiDevice(deviceInfo, pfd);
        }

        public MidiDevice[] newArray(int size) {
            return new MidiDevice[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeParcelable(mDeviceInfo, flags);
        parcel.writeParcelable(mParcelFileDescriptor, flags);
   }

    /**
     * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
     *
     * message byte array contains variable length MIDI message.
     * messageSize is size of variable length MIDI message
     * timestamp is message timestamp to pack
     * dest is buffer to pack into
     * returns size of packed message
     *
     * @hide
     */
    public static int packMessage(byte[] message, int offset, int size, long timestamp,
            int portNumber, byte[] dest) {
        // pack variable length message first
        System.arraycopy(message, offset, dest, 0, size);
        int destOffset = size;
        // timestamp takes 8 bytes
        for (int i = 0; i < 8; i++) {
            dest[destOffset++] = (byte)timestamp;
            timestamp >>= 8;
        }
        // portNumber is last
        dest[destOffset++] = (byte)portNumber;

        return destOffset;
    }

    /**
     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
     * returns the offet of of MIDI message in packed buffer
     *
     * @hide
     */
    public static int getMessageOffset(byte[] buffer, int bufferLength) {
        // message is at start of buffer
        return 0;
    }

    /**
     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
     * returns size of MIDI message in packed buffer
     *
     * @hide
     */
    public static int getMessageSize(byte[] buffer, int bufferLength) {
        // message length is total buffer length minus size of the timestamp and port number
        return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */;
    }

    /**
     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
     * unpacks timestamp from packed buffer
     *
     * @hide
     */
    public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
        long timestamp = 0;

        // timestamp follows variable length message data
        int dataLength = getMessageSize(buffer, bufferLength);
        for (int i = dataLength + 7; i >= dataLength; i--) {
            // why can't Java deal with unsigned ints?
            int b = buffer[i];
            if (b < 0) b += 256;
            timestamp = (timestamp << 8) | b;
        }
        return timestamp;
     }

    /**
     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
     * unpacks port number from packed buffer
     *
     * @hide
     */
    public static int getMessagePortNumber(byte[] buffer, int bufferLength) {
        // timestamp follows variable length message data and timestamp
        int dataLength = getMessageSize(buffer, bufferLength);
        return buffer[dataLength + 8 /* sizeof(timestamp) */];
        return ("MidiDevice: " + mDeviceInfo.toString());
    }
}
+14 −41
Original line number Diff line number Diff line
@@ -50,10 +50,6 @@ public class MidiDeviceInfo implements Parcelable {
    private final int mOutputPortCount;
    private final Bundle mProperties;

    // used for USB devices only
    private final int mAlsaCard;
    private final int mAlsaDevice;

    /**
     * Bundle key for the device's manufacturer name property.
     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
@@ -83,33 +79,30 @@ public class MidiDeviceInfo implements Parcelable {
    public static final String PROPERTY_USB_DEVICE = "usb_device";

    /**
     * MidiDeviceInfo should only be instantiated by MidiService implementation
     * @hide
     * 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}
     */
    public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
            Bundle properties) {
        mType = type;
        mId = id;
        mInputPortCount = numInputPorts;
        mOutputPortCount = numOutputPorts;
        mProperties = properties;
        mAlsaCard = -1;
        mAlsaDevice = -1;
    }
    public static final String PROPERTY_ALSA_CARD = "alsa_card";

    /**
     * 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}
     */
    public static final String PROPERTY_ALSA_DEVICE = "alsa_device";

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

    /**
@@ -158,20 +151,6 @@ public class MidiDeviceInfo implements Parcelable {
        return mProperties;
    }

    /**
     * @hide
     */
    public int getAlsaCard() {
        return mAlsaCard;
    }

    /**
     * @hide
     */
    public int getAlsaDevice() {
        return mAlsaDevice;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof MidiDeviceInfo) {
@@ -191,9 +170,7 @@ public class MidiDeviceInfo implements Parcelable {
        return ("MidiDeviceInfo[mType=" + mType +
                ",mInputPortCount=" + mInputPortCount +
                ",mOutputPortCount=" + mOutputPortCount +
                ",mProperties=" + mProperties +
                ",mAlsaCard=" + mAlsaCard +
                ",mAlsaDevice=" + mAlsaDevice);
                ",mProperties=" + mProperties);
    }

    public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -204,9 +181,7 @@ public class MidiDeviceInfo implements Parcelable {
            int inputPorts = in.readInt();
            int outputPorts = in.readInt();
            Bundle properties = in.readBundle();
            int card = in.readInt();
            int device = in.readInt();
            return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, card, device);
            return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
        }

        public MidiDeviceInfo[] newArray(int size) {
@@ -224,7 +199,5 @@ public class MidiDeviceInfo implements Parcelable {
        parcel.writeInt(mInputPortCount);
        parcel.writeInt(mOutputPortCount);
        parcel.writeBundle(mProperties);
        parcel.writeInt(mAlsaCard);
        parcel.writeInt(mAlsaDevice);
   }
}
Loading