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

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

Merge "UsbMidiDevice: Only keep ALSA devices open when device is in use" into mnc-dev

parents d951d65d 6d5a0f91
Loading
Loading
Loading
Loading
+26 −4
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ namespace android
{

static jclass sFileDescriptorClass;
static jfieldID sPipeFDField;

static jint
android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
@@ -66,14 +67,15 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */
}

static jobjectArray
android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint device,
        jint subdevice_count)
{
    char    path[100];

    snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);

    jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
    // allocate one extra file descriptor for close pipe
    jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL);
    if (!fds) {
        return NULL;
    }
@@ -91,12 +93,27 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, ji
        env->DeleteLocalRef(fileDescriptor);
    }

    // create a pipe to use for unblocking our input thread
    int pipeFD[2];
    pipe(pipeFD);
    jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]);
    env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor);
    env->DeleteLocalRef(fileDescriptor);
    // store our end of the pipe in mPipeFD
    env->SetIntField(thiz, sPipeFDField, pipeFD[1]);

    return fds;
}

static void
android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds)
android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds)
{
    // write to mPipeFD to unblock input thread
    jint pipeFD = env->GetIntField(thiz, sPipeFDField);
    write(pipeFD, &pipeFD, sizeof(pipeFD));
    close(pipeFD);
    env->SetIntField(thiz, sPipeFDField, -1);

    int count = env->GetArrayLength(fds);
    for (int i = 0; i < count; i++) {
        jobject fd = env->GetObjectArrayElement(fds, i);
@@ -117,13 +134,18 @@ int register_android_server_UsbMidiDevice(JNIEnv *env)
        ALOGE("Can't find java/io/FileDescriptor");
        return -1;
    }
    sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
    sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);

    clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
    if (clazz == NULL) {
        ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
        return -1;
    }
    sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I");
    if (sPipeFDField == NULL) {
        ALOGE("Can't find UsbMidiDevice.mPipeFD");
        return -1;
    }

    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
            method_table, NELEM(method_table));
+170 −59
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.usb;
import android.content.Context;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
import android.media.midi.MidiDeviceStatus;
import android.media.midi.MidiManager;
import android.media.midi.MidiReceiver;
import android.media.midi.MidiSender;
@@ -43,38 +44,100 @@ import java.io.IOException;
public final class UsbMidiDevice implements Closeable {
    private static final String TAG = "UsbMidiDevice";

    private final int mAlsaCard;
    private final int mAlsaDevice;
    private final int mSubdeviceCount;
    private final InputReceiverProxy[] mInputPortReceivers;

    private MidiDeviceServer mServer;

    // event schedulers for each output port
    private final MidiEventScheduler[] mEventSchedulers;
    private MidiEventScheduler[] mEventSchedulers;

    private static final int BUFFER_SIZE = 512;

    private final FileDescriptor[] mFileDescriptors;
    private FileDescriptor[] mFileDescriptors;

    // for polling multiple FileDescriptors for MIDI events
    private final StructPollfd[] mPollFDs;
    private StructPollfd[] mPollFDs;
    // streams for reading from ALSA driver
    private final FileInputStream[] mInputStreams;
    private FileInputStream[] mInputStreams;
    // streams for writing to ALSA driver
    private final FileOutputStream[] mOutputStreams;
    private FileOutputStream[] mOutputStreams;

    public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
        // FIXME - support devices with different number of input and output ports
        int subDevices = nativeGetSubdeviceCount(card, device);
        if (subDevices <= 0) {
            Log.e(TAG, "nativeGetSubdeviceCount failed");
            return null;
    private final Object mLock = new Object();
    private boolean mIsOpen;

    // pipe file descriptor for signalling input thread to exit
    // only accessed from JNI code
    private int mPipeFD = -1;

    private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {

        @Override
        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
            MidiDeviceInfo deviceInfo = status.getDeviceInfo();
            int inputPorts = deviceInfo.getInputPortCount();
            int outputPorts = deviceInfo.getOutputPortCount();
            boolean hasOpenPorts = false;

            for (int i = 0; i < inputPorts; i++) {
                if (status.isInputPortOpen(i)) {
                    hasOpenPorts = true;
                    break;
                }
            }

            if (!hasOpenPorts) {
                for (int i = 0; i < outputPorts; i++) {
                    if (status.getOutputPortOpenCount(i) > 0) {
                        hasOpenPorts = true;
                        break;
                    }
                }
            }

            synchronized (mLock) {
                if (hasOpenPorts && !mIsOpen) {
                    openLocked();
                } else if (!hasOpenPorts && mIsOpen) {
                    closeLocked();
                }
            }
        }

        @Override
        public void onClose() {
        }
    };

    // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
    // until the device has active clients
    private final class InputReceiverProxy extends MidiReceiver {
        private MidiReceiver mReceiver;

        @Override
        public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
            MidiReceiver receiver = mReceiver;
            if (receiver != null) {
                receiver.send(msg, offset, count, timestamp);
            }
        }

        public void setReceiver(MidiReceiver receiver) {
            mReceiver = receiver;
        }
    }
    
    public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
        // FIXME - support devices with different number of input and output ports
        FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
        if (fileDescriptors == null) {
            Log.e(TAG, "nativeOpen failed");
        int subDeviceCount = nativeGetSubdeviceCount(card, device);
        if (subDeviceCount <= 0) {
            Log.e(TAG, "nativeGetSubdeviceCount failed");
            return null;
        }

        UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors);
        UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
        if (!midiDevice.register(context, properties)) {
            IoUtils.closeQuietly(midiDevice);
            Log.e(TAG, "createDeviceServer failed");
@@ -83,10 +146,32 @@ public final class UsbMidiDevice implements Closeable {
        return midiDevice;
    }

    private UsbMidiDevice(FileDescriptor[] fileDescriptors) {
    private UsbMidiDevice(int card, int device, int subdeviceCount) {
        mAlsaCard = card;
        mAlsaDevice = device;
        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++) {
            mInputPortReceivers[port] = new InputReceiverProxy();
        }
    }

    private boolean openLocked() {
        // FIXME - support devices with different number of input and output ports
        FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount);
        if (fileDescriptors == null) {
            Log.e(TAG, "nativeOpen failed");
            return false;
        }

        mFileDescriptors = fileDescriptors;
        int inputCount = fileDescriptors.length;
        int outputCount = fileDescriptors.length;
        // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
        // in our input thread
        int outputCount = fileDescriptors.length - 1;

        mPollFDs = new StructPollfd[inputCount];
        mInputStreams = new FileInputStream[inputCount];
@@ -103,29 +188,12 @@ public final class UsbMidiDevice implements Closeable {
        mEventSchedulers = new MidiEventScheduler[outputCount];
        for (int i = 0; i < outputCount; i++) {
            mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
            mEventSchedulers[i] = new MidiEventScheduler();
        }
    }

    private boolean register(Context context, Bundle properties) {
        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
        if (midiManager == null) {
            Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
            return false;
        }

        int inputCount = mInputStreams.length;
        int outputCount = mOutputStreams.length;
        MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
        for (int port = 0; port < inputCount; port++) {
            inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
            MidiEventScheduler scheduler = new MidiEventScheduler();
            mEventSchedulers[i] = scheduler;
            mInputPortReceivers[i].setReceiver(scheduler.getReceiver());
        }

        mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
                null, null, properties, MidiDeviceInfo.TYPE_USB, null);
        if (mServer == null) {
            return false;
        }
        final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();

        // Create input thread which will read from all input ports
@@ -134,24 +202,32 @@ public final class UsbMidiDevice implements Closeable {
            public void run() {
                byte[] buffer = new byte[BUFFER_SIZE];
                try {
                    boolean done = false;
                    while (!done) {
                    while (true) {
                        synchronized (mLock) {
                            if (!mIsOpen) break;

                            // look for a readable FileDescriptor
                            for (int index = 0; index < mPollFDs.length; index++) {
                                StructPollfd pfd = mPollFDs[index];
                            if ((pfd.revents & OsConstants.POLLIN) != 0) {
                                if ((pfd.revents & (OsConstants.POLLERR
                                                            | OsConstants.POLLHUP)) != 0) {
                                    break;
                                } else if ((pfd.revents & OsConstants.POLLIN) != 0) {
                                    // clear readable flag
                                    pfd.revents = 0;
                                    
                                    if (index == mInputStreams.length - 1) {
                                        // last file descriptor is used only for unblocking Os.poll()
                                        break;
                                    }

                                    int count = mInputStreams[index].read(buffer);
                                    outputReceivers[index].send(buffer, 0, count);
                            } else if ((pfd.revents & (OsConstants.POLLERR
                                                        | OsConstants.POLLHUP)) != 0) {
                                done = true;
                                }
                            }
                        }

                        // wait until we have a readable port
                        // wait until we have a readable port or we are signalled to close
                        Os.poll(mPollFDs, -1 /* infinite timeout */);
                     }
                } catch (IOException e) {
@@ -195,29 +271,64 @@ public final class UsbMidiDevice implements Closeable {
            }.start();
        }

        mIsOpen = true;
        return true;
    }

    private boolean register(Context context, Bundle properties) {
        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
        if (midiManager == null) {
            Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
            return false;
        }

        mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
                null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
        if (mServer == null) {
            return false;
        }

        return true;
    }

    @Override
    public void close() throws IOException {
        for (int i = 0; i < mEventSchedulers.length; i++) {
            mEventSchedulers[i].close();
        synchronized (mLock) {
            if (mIsOpen) {
                closeLocked();
            }
        }

        if (mServer != null) {
            mServer.close();
            IoUtils.closeQuietly(mServer);
        }
    }

    private void closeLocked() {
        for (int i = 0; i < mEventSchedulers.length; i++) {
            mInputPortReceivers[i].setReceiver(null);
            mEventSchedulers[i].close();
        }
        mEventSchedulers = null;

        for (int i = 0; i < mInputStreams.length; i++) {
            mInputStreams[i].close();
            IoUtils.closeQuietly(mInputStreams[i]);
        }
        mInputStreams = null;

        for (int i = 0; i < mOutputStreams.length; i++) {
            mOutputStreams[i].close();
            IoUtils.closeQuietly(mOutputStreams[i]);
        }
        mOutputStreams = null;

        // nativeClose will close the file descriptors and signal the input thread to exit
        nativeClose(mFileDescriptors);
        mFileDescriptors = null;

        mIsOpen = false;
    }

    private static native int nativeGetSubdeviceCount(int card, int device);
    private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
    private static native void nativeClose(FileDescriptor[] fileDescriptors);
    private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
    private native void nativeClose(FileDescriptor[] fileDescriptors);
}