110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood/*
210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * Copyright (C) 2014 The Android Open Source Project
310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood *
410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * Licensed under the Apache License, Version 2.0 (the "License");
510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * you may not use this file except in compliance with the License.
610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * You may obtain a copy of the License at
710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood *
810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood *      http://www.apache.org/licenses/LICENSE-2.0
910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood *
1010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * Unless required by applicable law or agreed to in writing, software
1110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * distributed under the License is distributed on an "AS IS" BASIS,
1210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * See the License for the specific language governing permissions an
1410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * limitations under the License.
1510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood */
1610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodpackage com.android.server.usb;
1810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.content.Context;
20b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodimport android.media.midi.MidiDeviceInfo;
21b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodimport android.media.midi.MidiDeviceServer;
226d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwoodimport android.media.midi.MidiDeviceStatus;
23b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodimport android.media.midi.MidiManager;
24b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodimport android.media.midi.MidiReceiver;
25b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodimport android.media.midi.MidiSender;
2610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.os.Bundle;
2710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.system.ErrnoException;
2810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.system.Os;
2910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.system.OsConstants;
3010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.system.StructPollfd;
3110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.util.Log;
3210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
332776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwoodimport com.android.internal.midi.MidiEventScheduler;
342776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwoodimport com.android.internal.midi.MidiEventScheduler.MidiEvent;
352776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood
3611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwoodimport libcore.io.IoUtils;
3711fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
3810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.Closeable;
3910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.FileDescriptor;
4010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.FileInputStream;
4110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.FileOutputStream;
4210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.IOException;
4310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
4410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodpublic final class UsbMidiDevice implements Closeable {
4510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private static final String TAG = "UsbMidiDevice";
4610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
476d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final int mAlsaCard;
486d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final int mAlsaDevice;
496d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final int mSubdeviceCount;
506d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final InputReceiverProxy[] mInputPortReceivers;
516d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
5211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private MidiDeviceServer mServer;
5311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
54aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov    // event schedulers for each input port of the physical device
556d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private MidiEventScheduler[] mEventSchedulers;
5610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
5720821ecbe81ba52b260ae232096bc2bfb3e92ad0Mike Lockwood    private static final int BUFFER_SIZE = 512;
5820821ecbe81ba52b260ae232096bc2bfb3e92ad0Mike Lockwood
596d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private FileDescriptor[] mFileDescriptors;
60b7ce094c9e546c4a802bd8ce3a43592979a5e3dfMike Lockwood
6110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    // for polling multiple FileDescriptors for MIDI events
626d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private StructPollfd[] mPollFDs;
63cb096273734d96f4676014fa9030b57ea48b58d8Mike Lockwood    // streams for reading from ALSA driver
646d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private FileInputStream[] mInputStreams;
65cb096273734d96f4676014fa9030b57ea48b58d8Mike Lockwood    // streams for writing to ALSA driver
666d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private FileOutputStream[] mOutputStreams;
6710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
686d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final Object mLock = new Object();
696d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private boolean mIsOpen;
706d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
716d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    // pipe file descriptor for signalling input thread to exit
726d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    // only accessed from JNI code
736d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private int mPipeFD = -1;
746d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
756d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
766d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
776d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        @Override
786d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
796d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            MidiDeviceInfo deviceInfo = status.getDeviceInfo();
806d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            int inputPorts = deviceInfo.getInputPortCount();
816d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            int outputPorts = deviceInfo.getOutputPortCount();
826d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            boolean hasOpenPorts = false;
836d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
846d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            for (int i = 0; i < inputPorts; i++) {
856d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                if (status.isInputPortOpen(i)) {
866d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    hasOpenPorts = true;
876d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    break;
886d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                }
896d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            }
906d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
916d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            if (!hasOpenPorts) {
926d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                for (int i = 0; i < outputPorts; i++) {
936d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    if (status.getOutputPortOpenCount(i) > 0) {
946d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                        hasOpenPorts = true;
956d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                        break;
966d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    }
976d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                }
986d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            }
996d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1006d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            synchronized (mLock) {
1016d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                if (hasOpenPorts && !mIsOpen) {
1026d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    openLocked();
1036d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                } else if (!hasOpenPorts && mIsOpen) {
1046d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    closeLocked();
1056d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                }
1066d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            }
1076d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
1086d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1096d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        @Override
1106d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        public void onClose() {
1116d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
1126d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    };
1136d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1146d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
1156d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    // until the device has active clients
1166d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private final class InputReceiverProxy extends MidiReceiver {
1176d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        private MidiReceiver mReceiver;
1186d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1196d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        @Override
1206d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
1216d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            MidiReceiver receiver = mReceiver;
1226d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            if (receiver != null) {
1236d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                receiver.send(msg, offset, count, timestamp);
1246d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            }
12510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
12610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1276d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        public void setReceiver(MidiReceiver receiver) {
1286d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mReceiver = receiver;
1296d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
130de2922468be26ab649ee44e7af291d97aebf0701Phil Burk
131de2922468be26ab649ee44e7af291d97aebf0701Phil Burk        @Override
132de2922468be26ab649ee44e7af291d97aebf0701Phil Burk        public void onFlush() throws IOException {
133de2922468be26ab649ee44e7af291d97aebf0701Phil Burk            MidiReceiver receiver = mReceiver;
134de2922468be26ab649ee44e7af291d97aebf0701Phil Burk            if (receiver != null) {
135de2922468be26ab649ee44e7af291d97aebf0701Phil Burk                receiver.flush();
136de2922468be26ab649ee44e7af291d97aebf0701Phil Burk            }
137de2922468be26ab649ee44e7af291d97aebf0701Phil Burk        }
1386d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    }
1396456a65afed66819b73df7eb605037402b91fc3fPhil Burk
1406d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
14110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        // FIXME - support devices with different number of input and output ports
1426d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        int subDeviceCount = nativeGetSubdeviceCount(card, device);
1436d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        if (subDeviceCount <= 0) {
1446d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            Log.e(TAG, "nativeGetSubdeviceCount failed");
14510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            return null;
14610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
14710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1486d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
14911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        if (!midiDevice.register(context, properties)) {
15011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            IoUtils.closeQuietly(midiDevice);
15110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            Log.e(TAG, "createDeviceServer failed");
15210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            return null;
15310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
15411fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        return midiDevice;
15510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
15610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1576d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private UsbMidiDevice(int card, int device, int subdeviceCount) {
1586d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mAlsaCard = card;
1596d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mAlsaDevice = device;
1606d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mSubdeviceCount = subdeviceCount;
1616d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1626d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        // FIXME - support devices with different number of input and output ports
163aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        int inputPortCount = subdeviceCount;
164aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        mInputPortReceivers = new InputReceiverProxy[inputPortCount];
165aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        for (int port = 0; port < inputPortCount; port++) {
1666d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mInputPortReceivers[port] = new InputReceiverProxy();
1676d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
1686d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    }
1696d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
1706d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private boolean openLocked() {
1716d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        // FIXME - support devices with different number of input and output ports
1726d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount);
1736d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        if (fileDescriptors == null) {
1746d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            Log.e(TAG, "nativeOpen failed");
1756d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            return false;
1766d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
1776d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
178b7ce094c9e546c4a802bd8ce3a43592979a5e3dfMike Lockwood        mFileDescriptors = fileDescriptors;
179aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        int inputStreamCount = fileDescriptors.length;
1806d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
1816d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        // in our input thread
182aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        int outputStreamCount = fileDescriptors.length - 1;
18310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
184aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        mPollFDs = new StructPollfd[inputStreamCount];
185aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        mInputStreams = new FileInputStream[inputStreamCount];
186aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        for (int i = 0; i < inputStreamCount; i++) {
187b7ce094c9e546c4a802bd8ce3a43592979a5e3dfMike Lockwood            FileDescriptor fd = fileDescriptors[i];
18810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            StructPollfd pollfd = new StructPollfd();
18910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            pollfd.fd = fd;
19010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            pollfd.events = (short)OsConstants.POLLIN;
19110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            mPollFDs[i] = pollfd;
19210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            mInputStreams[i] = new FileInputStream(fd);
19310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
19410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
195aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        mOutputStreams = new FileOutputStream[outputStreamCount];
196aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        mEventSchedulers = new MidiEventScheduler[outputStreamCount];
197aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        for (int i = 0; i < outputStreamCount; i++) {
198b7ce094c9e546c4a802bd8ce3a43592979a5e3dfMike Lockwood            mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
19910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
2006d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            MidiEventScheduler scheduler = new MidiEventScheduler();
2016d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mEventSchedulers[i] = scheduler;
2026d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mInputPortReceivers[i].setReceiver(scheduler.getReceiver());
20311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        }
20411fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
20511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
20611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
207aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        // Create input thread which will read from all output ports of the physical device
2082776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood        new Thread("UsbMidiDevice input thread") {
20910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            @Override
21010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            public void run() {
21120821ecbe81ba52b260ae232096bc2bfb3e92ad0Mike Lockwood                byte[] buffer = new byte[BUFFER_SIZE];
21210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                try {
2136d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                    while (true) {
2146456a65afed66819b73df7eb605037402b91fc3fPhil Burk                        // Record time of event immediately after waking.
2156456a65afed66819b73df7eb605037402b91fc3fPhil Burk                        long timestamp = System.nanoTime();
2166d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                        synchronized (mLock) {
2176d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                            if (!mIsOpen) break;
2186d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
2196d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                            // look for a readable FileDescriptor
2206d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                            for (int index = 0; index < mPollFDs.length; index++) {
2216d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                StructPollfd pfd = mPollFDs[index];
2226d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                if ((pfd.revents & (OsConstants.POLLERR
2236d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                                            | OsConstants.POLLHUP)) != 0) {
2246d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    break;
2256d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                } else if ((pfd.revents & OsConstants.POLLIN) != 0) {
2266d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    // clear readable flag
2276d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    pfd.revents = 0;
2286456a65afed66819b73df7eb605037402b91fc3fPhil Burk
2296d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    if (index == mInputStreams.length - 1) {
2306d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                        // last file descriptor is used only for unblocking Os.poll()
2316d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                        break;
2326d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    }
2336d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
2346d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                    int count = mInputStreams[index].read(buffer);
2356456a65afed66819b73df7eb605037402b91fc3fPhil Burk                                    outputReceivers[index].send(buffer, 0, count, timestamp);
2366d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                                }
23710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                            }
23810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                        }
23910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
2406d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                        // wait until we have a readable port or we are signalled to close
24110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                        Os.poll(mPollFDs, -1 /* infinite timeout */);
24210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                     }
24310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                } catch (IOException e) {
24410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                    Log.d(TAG, "reader thread exiting");
24510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                } catch (ErrnoException e) {
24610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                    Log.d(TAG, "reader thread exiting");
24710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                }
2482776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood                Log.d(TAG, "input thread exit");
2492776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood            }
2502776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood        }.start();
2512776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood
252aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        // Create output thread for each input port of the physical device
253aab4c1ad489a8b62186215d826293e9cbcbaeb85Mikhail Naganov        for (int port = 0; port < outputStreamCount; port++) {
254b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood            final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
255b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood            final FileOutputStream outputStreamF = mOutputStreams[port];
256b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood            final int portF = port;
257b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood
258b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood            new Thread("UsbMidiDevice output thread " + port) {
259b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                @Override
260b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                public void run() {
261b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                    while (true) {
262b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        MidiEvent event;
263b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        try {
264b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            event = (MidiEvent)eventSchedulerF.waitNextEvent();
265b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        } catch (InterruptedException e) {
266b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            // try again
267b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            continue;
268b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        }
269b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        if (event == null) {
270b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            break;
271b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        }
272b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        try {
273b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            outputStreamF.write(event.data, 0, event.count);
274b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        } catch (IOException e) {
275b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                            Log.e(TAG, "write failed for port " + portF);
276b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        }
277b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                        eventSchedulerF.addEventToPool(event);
2782776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood                    }
279b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood                    Log.d(TAG, "output thread exit");
2802776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood                }
281b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood            }.start();
282b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood        }
28311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
2846d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mIsOpen = true;
2856d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        return true;
2866d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    }
2876d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
2886d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private boolean register(Context context, Bundle properties) {
2896d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
2906d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        if (midiManager == null) {
2916d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
2926d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            return false;
2936d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
2946d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
2956d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
2966d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
2976d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        if (mServer == null) {
2986d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            return false;
2996d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
3006d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
30111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        return true;
30210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
30310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
30410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    @Override
30510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    public void close() throws IOException {
3066d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        synchronized (mLock) {
3076d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            if (mIsOpen) {
3086d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood                closeLocked();
3096d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            }
310b6f50d357bd3d4d296be6bb047f5ce93a79cbca1Mike Lockwood        }
3112776133be7ac60dc8d6aea5b12e35449ca331836Mike Lockwood
31211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        if (mServer != null) {
3136d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            IoUtils.closeQuietly(mServer);
3146d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        }
3156d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    }
3166d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
3176d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private void closeLocked() {
3186d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        for (int i = 0; i < mEventSchedulers.length; i++) {
3196d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mInputPortReceivers[i].setReceiver(null);
3206d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            mEventSchedulers[i].close();
32111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        }
3226d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mEventSchedulers = null;
32310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
32410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        for (int i = 0; i < mInputStreams.length; i++) {
3256d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            IoUtils.closeQuietly(mInputStreams[i]);
32610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
3276d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mInputStreams = null;
3286d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
32910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        for (int i = 0; i < mOutputStreams.length; i++) {
3306d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood            IoUtils.closeQuietly(mOutputStreams[i]);
33110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
3326d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mOutputStreams = null;
3336d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
3346d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        // nativeClose will close the file descriptors and signal the input thread to exit
335b7ce094c9e546c4a802bd8ce3a43592979a5e3dfMike Lockwood        nativeClose(mFileDescriptors);
3366d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mFileDescriptors = null;
3376d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood
3386d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood        mIsOpen = false;
33910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
34010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
34110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private static native int nativeGetSubdeviceCount(int card, int device);
3426d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
3436d5a0f916499a69f28b860fd66d09b5025c30450Mike Lockwood    private native void nativeClose(FileDescriptor[] fileDescriptors);
34410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood}
345