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