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 
17 package android.media.midi;
18 
19 import android.os.IBinder;
20 import android.os.RemoteException;
21 import android.util.Log;
22 
23 import dalvik.system.CloseGuard;
24 
25 import libcore.io.IoUtils;
26 
27 import java.io.Closeable;
28 import java.io.FileDescriptor;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 
32 /**
33  * This class is used for sending data to a port on a MIDI device
34  */
35 public 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 FileDescriptor mFileDescriptor;
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 
MidiInputPort(IMidiDeviceServer server, IBinder token, FileDescriptor fd, int portNumber)50     /* package */ MidiInputPort(IMidiDeviceServer server, IBinder token,
51             FileDescriptor fd, int portNumber) {
52         super(MidiPortImpl.MAX_PACKET_DATA_SIZE);
53 
54         mDeviceServer = server;
55         mToken = token;
56         mFileDescriptor = fd;
57         mPortNumber = portNumber;
58         mOutputStream = new FileOutputStream(fd);
59         mGuard.open("close");
60     }
61 
MidiInputPort(FileDescriptor fd, int portNumber)62     /* package */ MidiInputPort(FileDescriptor fd, int portNumber) {
63         this(null, null, fd, portNumber);
64     }
65 
66     /**
67      * Returns the port number of this port
68      *
69      * @return the port's port number
70      */
getPortNumber()71     public final int getPortNumber() {
72         return mPortNumber;
73     }
74 
75     @Override
onSend(byte[] msg, int offset, int count, long timestamp)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
onFlush()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
claimFileDescriptor()105     /* package */ FileDescriptor claimFileDescriptor() {
106         synchronized (mGuard) {
107             FileDescriptor fd;
108             synchronized (mBuffer) {
109                 fd = mFileDescriptor;
110                 if (fd == null) return null;
111                 IoUtils.closeQuietly(mOutputStream);
112                 mFileDescriptor = 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 fd;
120         }
121     }
122 
123     // used by MidiDevice.MidiConnection to close this port after the connection is closed
getToken()124     /* package */ IBinder getToken() {
125         return mToken;
126     }
127 
128     // used by MidiDevice.MidiConnection to close this port after the connection is closed
getDeviceServer()129     /* package */ IMidiDeviceServer getDeviceServer() {
130         return mDeviceServer;
131     }
132 
133     @Override
close()134     public void close() throws IOException {
135         synchronized (mGuard) {
136             if (mIsClosed) return;
137             mGuard.close();
138             synchronized (mBuffer) {
139                 if (mFileDescriptor != null) {
140                     IoUtils.closeQuietly(mFileDescriptor);
141                     mFileDescriptor = 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
finalize()160     protected void finalize() throws Throwable {
161         try {
162             if (mGuard != null) {
163                 mGuard.warnIfOpen();
164             }
165 
166             // not safe to make binder calls from finalize()
167             mDeviceServer = null;
168             close();
169         } finally {
170             super.finalize();
171         }
172     }
173 }
174