1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media.midi;
18
19import android.os.IBinder;
20import android.os.ParcelFileDescriptor;
21import android.os.RemoteException;
22import android.util.Log;
23
24import dalvik.system.CloseGuard;
25
26import libcore.io.IoUtils;
27
28import java.io.Closeable;
29import java.io.FileOutputStream;
30import java.io.IOException;
31
32/**
33 * This class is used for sending data to a port on a MIDI device
34 */
35public final class MidiInputPort extends MidiReceiver implements Closeable {
36    private static final String TAG = "MidiInputPort";
37
38    private IMidiDeviceServer mDeviceServer;
39    private final IBinder mToken;
40    private final int mPortNumber;
41    private ParcelFileDescriptor mParcelFileDescriptor;
42    private FileOutputStream mOutputStream;
43
44    private final CloseGuard mGuard = CloseGuard.get();
45    private boolean mIsClosed;
46
47    // buffer to use for sending data out our output stream
48    private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
49
50    /* package */ MidiInputPort(IMidiDeviceServer server, IBinder token,
51            ParcelFileDescriptor pfd, int portNumber) {
52        super(MidiPortImpl.MAX_PACKET_DATA_SIZE);
53
54        mDeviceServer = server;
55        mToken = token;
56        mParcelFileDescriptor = pfd;
57        mPortNumber = portNumber;
58        mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
59        mGuard.open("close");
60    }
61
62    /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
63        this(null, null, pfd, portNumber);
64    }
65
66    /**
67     * Returns the port number of this port
68     *
69     * @return the port's port number
70     */
71    public final int getPortNumber() {
72        return mPortNumber;
73    }
74
75    @Override
76    public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
77        if (offset < 0 || count < 0 || offset + count > msg.length) {
78            throw new IllegalArgumentException("offset or count out of range");
79        }
80        if (count > MidiPortImpl.MAX_PACKET_DATA_SIZE) {
81            throw new IllegalArgumentException("count exceeds max message size");
82        }
83
84        synchronized (mBuffer) {
85            if (mOutputStream == null) {
86                throw new IOException("MidiInputPort is closed");
87            }
88            int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
89            mOutputStream.write(mBuffer, 0, length);
90        }
91    }
92
93    @Override
94    public void onFlush() throws IOException {
95        synchronized (mBuffer) {
96            if (mOutputStream == null) {
97                throw new IOException("MidiInputPort is closed");
98            }
99            int length = MidiPortImpl.packFlush(mBuffer);
100            mOutputStream.write(mBuffer, 0, length);
101        }
102    }
103
104    // used by MidiDevice.connectInputPort() to connect our socket directly to another device
105    /* package */ ParcelFileDescriptor claimFileDescriptor() {
106        synchronized (mGuard) {
107            ParcelFileDescriptor pfd;
108            synchronized (mBuffer) {
109                pfd = mParcelFileDescriptor;
110                if (pfd == null) return null;
111                IoUtils.closeQuietly(mOutputStream);
112                mParcelFileDescriptor = null;
113                mOutputStream = null;
114            }
115
116            // Set mIsClosed = true so we will not call mDeviceServer.closePort() in close().
117            // MidiDevice.MidiConnection.close() will do the cleanup instead.
118            mIsClosed = true;
119            return pfd;
120        }
121    }
122
123    // used by MidiDevice.MidiConnection to close this port after the connection is closed
124    /* package */ IBinder getToken() {
125        return mToken;
126    }
127
128    // used by MidiDevice.MidiConnection to close this port after the connection is closed
129    /* package */ IMidiDeviceServer getDeviceServer() {
130        return mDeviceServer;
131    }
132
133    @Override
134    public void close() throws IOException {
135        synchronized (mGuard) {
136            if (mIsClosed) return;
137            mGuard.close();
138            synchronized (mBuffer) {
139                if (mParcelFileDescriptor != null) {
140                    mParcelFileDescriptor.close();
141                    mParcelFileDescriptor = null;
142                }
143                if (mOutputStream != null) {
144                    mOutputStream.close();
145                    mOutputStream = null;
146                }
147            }
148            if (mDeviceServer != null) {
149                try {
150                    mDeviceServer.closePort(mToken);
151                } catch (RemoteException e) {
152                    Log.e(TAG, "RemoteException in MidiInputPort.close()");
153                }
154            }
155            mIsClosed = true;
156        }
157    }
158
159    @Override
160    protected void finalize() throws Throwable {
161        try {
162            mGuard.warnIfOpen();
163            // not safe to make binder calls from finalize()
164            mDeviceServer = null;
165            close();
166        } finally {
167            super.finalize();
168        }
169    }
170}
171