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 and
1410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood * limitations under the License.
1510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood */
1610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
17b673770f7172d4fca9bc05de1f36bc53e93eb247Mike Lockwoodpackage android.media.midi;
1810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
1911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwoodimport android.os.Binder;
205ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwoodimport android.os.IBinder;
2111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwoodimport android.os.Process;
2210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.os.RemoteException;
2317bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganovimport android.system.ErrnoException;
2417bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganovimport android.system.Os;
2510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.system.OsConstants;
2610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport android.util.Log;
2710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
28d1b16fe2fb7527eee214898263ec4d6dabbfb0b4Mike Lockwoodimport com.android.internal.midi.MidiDispatcher;
29d1b16fe2fb7527eee214898263ec4d6dabbfb0b4Mike Lockwood
30be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwoodimport dalvik.system.CloseGuard;
31be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
3211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwoodimport libcore.io.IoUtils;
3311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
3410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.Closeable;
3517bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganovimport java.io.FileDescriptor;
3610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodimport java.io.IOException;
374a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwoodimport java.util.HashMap;
38be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwoodimport java.util.concurrent.CopyOnWriteArrayList;
3910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
40a7e348eb4d2ef1632f2ebe3a742743607ccfd82bMike Lockwood/**
4111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood * Internal class used for providing an implementation for a MIDI device.
42a7e348eb4d2ef1632f2ebe3a742743607ccfd82bMike Lockwood *
43a7e348eb4d2ef1632f2ebe3a742743607ccfd82bMike Lockwood * @hide
44a7e348eb4d2ef1632f2ebe3a742743607ccfd82bMike Lockwood */
4510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwoodpublic final class MidiDeviceServer implements Closeable {
4610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private static final String TAG = "MidiDeviceServer";
4710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
4810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private final IMidiManager mMidiManager;
4910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
5010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    // MidiDeviceInfo for the device implemented by this server
5110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private MidiDeviceInfo mDeviceInfo;
5211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private final int mInputPortCount;
5311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private final int mOutputPortCount;
5410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
5511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    // MidiReceivers for receiving data on our input ports
5611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private final MidiReceiver[] mInputPortReceivers;
5710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
5811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    // MidiDispatchers for sending data on our output ports
5911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private MidiDispatcher[] mOutputPortDispatchers;
6010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
6111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    // MidiOutputPorts for clients connected to our input ports
6211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    private final MidiOutputPort[] mInputPortOutputPorts;
6310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
64be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    // List of all MidiInputPorts we created
65be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
66be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            = new CopyOnWriteArrayList<MidiInputPort>();
67be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
685ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
695ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    // for reporting device status
7081b9f7d325a552c54e793b51f571ae3d65b26e94Mike Lockwood    private final boolean[] mInputPortOpen;
715ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    private final int[] mOutputPortOpenCount;
725ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
73be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    private final CloseGuard mGuard = CloseGuard.get();
745ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    private boolean mIsClosed;
755ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
765ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    private final Callback mCallback;
775ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
78382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov    private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
79382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov    private final HashMap<MidiInputPort, PortClient> mInputPortClients =
80382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            new HashMap<MidiInputPort, PortClient>();
81382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov
825ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    public interface Callback {
835ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        /**
845ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood         * Called to notify when an our device status has changed
855ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood         * @param server the {@link MidiDeviceServer} that changed
865ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood         * @param status the {@link MidiDeviceStatus} for the device
875ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood         */
885ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
89e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood
90e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        /**
91e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood         * Called to notify when the device is closed
92e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood         */
93e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        public void onClose();
945ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    }
95be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
964a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood    abstract private class PortClient implements IBinder.DeathRecipient {
974a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        final IBinder mToken;
984a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
994a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        PortClient(IBinder token) {
1004a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            mToken = token;
1014a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1024a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            try {
1034a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                token.linkToDeath(this, 0);
1044a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            } catch (RemoteException e) {
1054a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                close();
1064a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            }
1074a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1084a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1094a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        abstract void close();
1104a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
111382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov        MidiInputPort getInputPort() {
112382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            return null;
113382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov        }
114382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov
1154a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        @Override
1164a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        public void binderDied() {
1174a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            close();
1184a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1194a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood    }
1204a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1214a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood    private class InputPortClient extends PortClient {
1224a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        private final MidiOutputPort mOutputPort;
1234a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1244a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        InputPortClient(IBinder token, MidiOutputPort outputPort) {
1254a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            super(token);
1264a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            mOutputPort = outputPort;
1274a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1284a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1294a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        @Override
1304a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        void close() {
1314a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            mToken.unlinkToDeath(this, 0);
1324a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            synchronized (mInputPortOutputPorts) {
1335ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                int portNumber = mOutputPort.getPortNumber();
1345ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                mInputPortOutputPorts[portNumber] = null;
13581b9f7d325a552c54e793b51f571ae3d65b26e94Mike Lockwood                mInputPortOpen[portNumber] = false;
1365ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                updateDeviceStatus();
1374a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            }
1384a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            IoUtils.closeQuietly(mOutputPort);
1394a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1404a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood    }
1414a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1424a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood    private class OutputPortClient extends PortClient {
1434a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        private final MidiInputPort mInputPort;
1444a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1454a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        OutputPortClient(IBinder token, MidiInputPort inputPort) {
1464a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            super(token);
1474a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            mInputPort = inputPort;
1484a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1494a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
1504a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        @Override
1514a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        void close() {
1524a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            mToken.unlinkToDeath(this, 0);
1535ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            int portNumber = mInputPort.getPortNumber();
1545ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
1555ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            synchronized (dispatcher) {
1565ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                dispatcher.getSender().disconnect(mInputPort);
1575ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                int openCount = dispatcher.getReceiverCount();
1585ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                mOutputPortOpenCount[portNumber] = openCount;
1595ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                updateDeviceStatus();
1605ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood           }
1615ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
162be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            mInputPorts.remove(mInputPort);
1634a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            IoUtils.closeQuietly(mInputPort);
1644a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
1654a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
166382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov        @Override
167382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov        MidiInputPort getInputPort() {
168382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            return mInputPort;
169382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov        }
170382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov    }
1714a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
17217bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov    private static FileDescriptor[] createSeqPacketSocketPair() throws IOException {
17317bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        try {
17417bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            final FileDescriptor fd0 = new FileDescriptor();
17517bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            final FileDescriptor fd1 = new FileDescriptor();
17617bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, fd0, fd1);
17717bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            return new FileDescriptor[] { fd0, fd1 };
17817bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        } catch (ErrnoException e) {
17917bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            throw e.rethrowAsIOException();
18017bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        }
18117bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov    }
18217bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov
18310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    // Binder interface stub for receiving connection requests from clients
18410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
18510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
18610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        @Override
18717bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        public FileDescriptor openInputPort(IBinder token, int portNumber) {
18811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            if (mDeviceInfo.isPrivate()) {
18911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                if (Binder.getCallingUid() != Process.myUid()) {
19011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                    throw new SecurityException("Can't access private device from different UID");
19111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                }
19211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            }
19311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
19410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            if (portNumber < 0 || portNumber >= mInputPortCount) {
19510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
19610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                return null;
19710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            }
19810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
19911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            synchronized (mInputPortOutputPorts) {
20011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                if (mInputPortOutputPorts[portNumber] != null) {
20110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                    Log.d(TAG, "port " + portNumber + " already open");
20210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                    return null;
20310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                }
20410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
20510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                try {
20617bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov                    FileDescriptor[] pair = createSeqPacketSocketPair();
2074a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
20811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                    mInputPortOutputPorts[portNumber] = outputPort;
2094a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    outputPort.connect(mInputPortReceivers[portNumber]);
2104a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    InputPortClient client = new InputPortClient(token, outputPort);
2114a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    synchronized (mPortClients) {
2124a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                        mPortClients.put(token, client);
2134a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    }
21481b9f7d325a552c54e793b51f571ae3d65b26e94Mike Lockwood                    mInputPortOpen[portNumber] = true;
2155ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                    updateDeviceStatus();
21611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                    return pair[1];
21798cc8e5a6473b3a5802d97cc81020ec4e3cd23f3Mike Lockwood                } catch (IOException e) {
21817bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov                    Log.e(TAG, "unable to create FileDescriptors in openInputPort");
21998cc8e5a6473b3a5802d97cc81020ec4e3cd23f3Mike Lockwood                    return null;
22010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                }
22110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            }
22210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
22310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
22410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        @Override
22517bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        public FileDescriptor openOutputPort(IBinder token, int portNumber) {
22611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            if (mDeviceInfo.isPrivate()) {
22711fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                if (Binder.getCallingUid() != Process.myUid()) {
22811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                    throw new SecurityException("Can't access private device from different UID");
22911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                }
23011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            }
23111fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
23210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            if (portNumber < 0 || portNumber >= mOutputPortCount) {
23310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
23410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood                return null;
23510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            }
23611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
23711fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            try {
23817bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov                FileDescriptor[] pair = createSeqPacketSocketPair();
2394a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
2407ea3e54b729e90770aab1275b31303ae65bcecc6Mikhail Naganov                // Undo the default blocking-mode of the server-side socket for
2417ea3e54b729e90770aab1275b31303ae65bcecc6Mikhail Naganov                // physical devices to avoid stalling the Java device handler if
2427ea3e54b729e90770aab1275b31303ae65bcecc6Mikhail Naganov                // client app code gets stuck inside 'onSend' handler.
2437ea3e54b729e90770aab1275b31303ae65bcecc6Mikhail Naganov                if (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL) {
24417bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov                    IoUtils.setBlocking(pair[0], false);
2457ea3e54b729e90770aab1275b31303ae65bcecc6Mikhail Naganov                }
2465ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
2475ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                synchronized (dispatcher) {
2485ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                    dispatcher.getSender().connect(inputPort);
2495ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                    int openCount = dispatcher.getReceiverCount();
2505ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                    mOutputPortOpenCount[portNumber] = openCount;
2515ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                    updateDeviceStatus();
2525ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                }
2535ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
254be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                mInputPorts.add(inputPort);
2554a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                OutputPortClient client = new OutputPortClient(token, inputPort);
2564a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                synchronized (mPortClients) {
2574a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    mPortClients.put(token, client);
2584a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                }
259382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                synchronized (mInputPortClients) {
260382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    mInputPortClients.put(inputPort, client);
261382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                }
26211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                return pair[1];
26311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood            } catch (IOException e) {
26417bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov                Log.e(TAG, "unable to create FileDescriptors in openOutputPort");
26511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood                return null;
26610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood            }
26710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
2684a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood
2694a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        @Override
2704a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        public void closePort(IBinder token) {
271382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            MidiInputPort inputPort = null;
2724a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            synchronized (mPortClients) {
2734a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                PortClient client = mPortClients.remove(token);
2744a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                if (client != null) {
275382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    inputPort = client.getInputPort();
2764a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                    client.close();
2774a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood                }
2784a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood            }
279382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            if (inputPort != null) {
280382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                synchronized (mInputPortClients) {
281382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    mInputPortClients.remove(inputPort);
282382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                }
283382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            }
2844a3d7ed45d98ad2fe900221755845b87f26b554aMike Lockwood        }
28546326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood
28646326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood        @Override
287e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        public void closeDevice() {
288e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            if (mCallback != null) {
289e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood                mCallback.onClose();
290e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            }
291e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            IoUtils.closeQuietly(MidiDeviceServer.this);
292e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        }
293e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood
294e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        @Override
29517bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov        public int connectPorts(IBinder token, FileDescriptor fd,
29646326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood                int outputPortNumber) {
29717bbb50edc8ccc56c4ecc932a19884c4cc1f5b6fMikhail Naganov            MidiInputPort inputPort = new MidiInputPort(fd, outputPortNumber);
2982aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood            MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber];
2992aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood            synchronized (dispatcher) {
3002aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood                dispatcher.getSender().connect(inputPort);
3012aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood                int openCount = dispatcher.getReceiverCount();
3022aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood                mOutputPortOpenCount[outputPortNumber] = openCount;
3032aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood                updateDeviceStatus();
3042aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood            }
3052aef7e3559e9e2c78287a00b3f693b6dc19e56f0Mike Lockwood
30646326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood            mInputPorts.add(inputPort);
30746326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood            OutputPortClient client = new OutputPortClient(token, inputPort);
30846326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood            synchronized (mPortClients) {
30946326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood                mPortClients.put(token, client);
31046326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood            }
311382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            synchronized (mInputPortClients) {
312382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                mInputPortClients.put(inputPort, client);
313382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            }
31480299748cc7ed5f59cb65122459b036b74150d4aPhil Burk            return Process.myPid(); // for caller to detect same process ID
31546326e59a0a19367d4158c027d56d4b8440e8d3dMike Lockwood        }
316f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
317f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
318f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public MidiDeviceInfo getDeviceInfo() {
319f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            return mDeviceInfo;
320f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
321acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood
322acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood        @Override
323acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood        public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
324acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
325acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood                throw new SecurityException("setDeviceInfo should only be called by MidiService");
326acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood            }
327acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood            if (mDeviceInfo != null) {
328acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood                throw new IllegalStateException("setDeviceInfo should only be called once");
329acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood            }
330acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood            mDeviceInfo = deviceInfo;
331acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood        }
33210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    };
33310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
334acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood    // Constructor for MidiManager.createDeviceServer()
33511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
3365ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            int numOutputPorts, Callback callback) {
33710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        mMidiManager = midiManager;
33811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        mInputPortReceivers = inputPortReceivers;
33911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        mInputPortCount = inputPortReceivers.length;
34011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        mOutputPortCount = numOutputPorts;
3415ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        mCallback = callback;
34211fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
34311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
34411fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood
34511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
34611fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        for (int i = 0; i < numOutputPorts; i++) {
347382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
34811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        }
349be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
35081b9f7d325a552c54e793b51f571ae3d65b26e94Mike Lockwood        mInputPortOpen = new boolean[mInputPortCount];
3515ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        mOutputPortOpenCount = new int[numOutputPorts];
3525ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
353be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood        mGuard.open("close");
35410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
35510024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
356382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov    private final MidiDispatcher.MidiReceiverFailureHandler mInputPortFailureHandler =
357382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            new MidiDispatcher.MidiReceiverFailureHandler() {
358382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                public void onReceiverFailure(MidiReceiver receiver, IOException failure) {
359382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    Log.e(TAG, "MidiInputPort failed to send data", failure);
360382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    PortClient client = null;
361382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    synchronized (mInputPortClients) {
362382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                        client = mInputPortClients.remove(receiver);
363382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    }
364382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    if (client != null) {
365382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                        client.close();
366382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                    }
367382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov                }
368382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov            };
369382cb48848cb3968d5a0088a825706d66cdfbd50Mikhail Naganov
370acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood    // Constructor for MidiDeviceService.onCreate()
371acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
372acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood           MidiDeviceInfo deviceInfo, Callback callback) {
373acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood        this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
374acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood        mDeviceInfo = deviceInfo;
375acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood    }
376acd4321872387212bbd956e134faf22eee4bbadcMike Lockwood
37710024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    /* package */ IMidiDeviceServer getBinderInterface() {
37810024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        return mServer;
37910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
38010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
381f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    public IBinder asBinder() {
382f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        return mServer.asBinder();
383f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
384f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
3855ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    private void updateDeviceStatus() {
3865ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        // clear calling identity, since we may be in a Binder call from one of our clients
3875ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        long identityToken = Binder.clearCallingIdentity();
3885ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
38981b9f7d325a552c54e793b51f571ae3d65b26e94Mike Lockwood        MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
3905ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood                mOutputPortOpenCount);
3915ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        if (mCallback != null) {
3925ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            mCallback.onDeviceStatusChanged(this, status);
3935ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        }
3945ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        try {
395e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            mMidiManager.setDeviceStatus(mServer, status);
3965ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        } catch (RemoteException e) {
3975ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            Log.e(TAG, "RemoteException in updateDeviceStatus");
3985ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        } finally {
3995ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            Binder.restoreCallingIdentity(identityToken);
4005ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood        }
4015ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood    }
4025ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood
40310024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    @Override
40410024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    public void close() throws IOException {
405be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood        synchronized (mGuard) {
4065ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            if (mIsClosed) return;
407be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            mGuard.close();
408be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
409be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            for (int i = 0; i < mInputPortCount; i++) {
410be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                MidiOutputPort outputPort = mInputPortOutputPorts[i];
411be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                if (outputPort != null) {
412be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                    IoUtils.closeQuietly(outputPort);
413be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                    mInputPortOutputPorts[i] = null;
414be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                }
415be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            }
416be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            for (MidiInputPort inputPort : mInputPorts) {
417be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                IoUtils.closeQuietly(inputPort);
418be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            }
419be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            mInputPorts.clear();
420be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            try {
421be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                mMidiManager.unregisterDeviceServer(mServer);
422be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            } catch (RemoteException e) {
423be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood                Log.e(TAG, "RemoteException in unregisterDeviceServer");
424be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            }
4255ff9e2a1719f78cddc7a23d6572ab15ab595dafdMike Lockwood            mIsClosed = true;
426be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood        }
427be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    }
428be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood
429be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    @Override
430be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood    protected void finalize() throws Throwable {
43110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        try {
432492e9e851cadca62df84eaff1a3c1ba788492fbaNarayan Kamath            if (mGuard != null) {
433492e9e851cadca62df84eaff1a3c1ba788492fbaNarayan Kamath                mGuard.warnIfOpen();
434492e9e851cadca62df84eaff1a3c1ba788492fbaNarayan Kamath            }
435492e9e851cadca62df84eaff1a3c1ba788492fbaNarayan Kamath
436be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            close();
437be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood        } finally {
438be215dd57282888b05b234c39bba44cc0a864b8aMike Lockwood            super.finalize();
43910024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood        }
44010024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
44110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood
44210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    /**
44311fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood     * Returns an array of {@link MidiReceiver} for the device's output ports.
44411fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood     * Clients can use these receivers to send data out the device's output ports.
44511fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood     * @return array of MidiReceivers
44610024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood     */
44711fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood    public MidiReceiver[] getOutputPortReceivers() {
44811fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
44911fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
45011fd96d6ff25bc1d710448eab545fe09da55a5f5Mike Lockwood        return receivers;
45110024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood    }
45210024b3dc12a8552c1547b67810c77b865045cc8Mike Lockwood}
453