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.ParcelFileDescriptor;
21 import android.os.RemoteException;
22 import android.util.Log;
23 
24 import com.android.internal.midi.MidiDispatcher;
25 
26 import dalvik.system.CloseGuard;
27 
28 import libcore.io.IoUtils;
29 
30 import java.io.Closeable;
31 import java.io.FileDescriptor;
32 import java.io.FileInputStream;
33 import java.io.IOException;
34 
35 /**
36  * This class is used for receiving data from a port on a MIDI device
37  */
38 public final class MidiOutputPort extends MidiSender implements Closeable {
39     private static final String TAG = "MidiOutputPort";
40 
41     private IMidiDeviceServer mDeviceServer;
42     private final IBinder mToken;
43     private final int mPortNumber;
44     private final FileInputStream mInputStream;
45     private final MidiDispatcher mDispatcher = new MidiDispatcher();
46 
47     private final CloseGuard mGuard = CloseGuard.get();
48     private boolean mIsClosed;
49 
50     // This thread reads MIDI events from a socket and distributes them to the list of
51     // MidiReceivers attached to this device.
52     private final Thread mThread = new Thread() {
53         @Override
54         public void run() {
55             byte[] buffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
56 
57             try {
58                 while (true) {
59                     // read next event
60                     int count = mInputStream.read(buffer);
61                     if (count < 0) {
62                         // This is the exit condition as read() returning <0 indicates
63                         // that the pipe has been closed.
64                         break;
65                         // FIXME - inform receivers here?
66                     }
67 
68                     int packetType = MidiPortImpl.getPacketType(buffer, count);
69                     switch (packetType) {
70                         case MidiPortImpl.PACKET_TYPE_DATA: {
71                             int offset = MidiPortImpl.getDataOffset(buffer, count);
72                             int size = MidiPortImpl.getDataSize(buffer, count);
73                             long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
74 
75                             // dispatch to all our receivers
76                             mDispatcher.send(buffer, offset, size, timestamp);
77                             break;
78                         }
79                         case MidiPortImpl.PACKET_TYPE_FLUSH:
80                             mDispatcher.flush();
81                             break;
82                         default:
83                             Log.e(TAG, "Unknown packet type " + packetType);
84                             break;
85                     }
86                 } // while (true)
87             } catch (IOException e) {
88                 // FIXME report I/O failure?
89                 // TODO: The comment above about the exit condition is not currently working
90                 // as intended. The read from the closed pipe is throwing an error rather than
91                 // returning <0, so this becomes (probably) not an error, but the exit case.
92                 // This warrants further investigation;
93                 // Silence the (probably) spurious error message.
94                 // Log.e(TAG, "read failed", e);
95             } finally {
96                 IoUtils.closeQuietly(mInputStream);
97             }
98         }
99     };
100 
MidiOutputPort(IMidiDeviceServer server, IBinder token, FileDescriptor fd, int portNumber)101     /* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
102             FileDescriptor fd, int portNumber) {
103         mDeviceServer = server;
104         mToken = token;
105         mPortNumber = portNumber;
106         mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(new ParcelFileDescriptor(fd));
107         mThread.start();
108         mGuard.open("close");
109     }
110 
MidiOutputPort(FileDescriptor fd, int portNumber)111     /* package */ MidiOutputPort(FileDescriptor fd, int portNumber) {
112         this(null, null, fd, portNumber);
113     }
114 
115     /**
116      * Returns the port number of this port
117      *
118      * @return the port's port number
119      */
getPortNumber()120     public final int getPortNumber() {
121         return mPortNumber;
122     }
123 
124     @Override
onConnect(MidiReceiver receiver)125     public void onConnect(MidiReceiver receiver) {
126         mDispatcher.getSender().connect(receiver);
127     }
128 
129     @Override
onDisconnect(MidiReceiver receiver)130     public void onDisconnect(MidiReceiver receiver) {
131         mDispatcher.getSender().disconnect(receiver);
132     }
133 
134     @Override
close()135     public void close() throws IOException {
136         synchronized (mGuard) {
137             if (mIsClosed) return;
138 
139             mGuard.close();
140             mInputStream.close();
141             if (mDeviceServer != null) {
142                 try {
143                     mDeviceServer.closePort(mToken);
144                 } catch (RemoteException e) {
145                     Log.e(TAG, "RemoteException in MidiOutputPort.close()");
146                 }
147             }
148             mIsClosed = true;
149         }
150     }
151 
152     @Override
finalize()153     protected void finalize() throws Throwable {
154         try {
155             if (mGuard != null) {
156                 mGuard.warnIfOpen();
157             }
158 
159             // not safe to make binder calls from finalize()
160             mDeviceServer = null;
161             close();
162         } finally {
163             super.finalize();
164         }
165     }
166 }
167