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                         break;
63                         // FIXME - inform receivers here?
64                     }
65 
66                     int packetType = MidiPortImpl.getPacketType(buffer, count);
67                     switch (packetType) {
68                         case MidiPortImpl.PACKET_TYPE_DATA: {
69                             int offset = MidiPortImpl.getDataOffset(buffer, count);
70                             int size = MidiPortImpl.getDataSize(buffer, count);
71                             long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
72 
73                             // dispatch to all our receivers
74                             mDispatcher.send(buffer, offset, size, timestamp);
75                             break;
76                         }
77                         case MidiPortImpl.PACKET_TYPE_FLUSH:
78                             mDispatcher.flush();
79                             break;
80                         default:
81                             Log.e(TAG, "Unknown packet type " + packetType);
82                             break;
83                     }
84                 }
85             } catch (IOException e) {
86                 // FIXME report I/O failure?
87                 Log.e(TAG, "read failed", e);
88             } finally {
89                 IoUtils.closeQuietly(mInputStream);
90             }
91         }
92     };
93 
MidiOutputPort(IMidiDeviceServer server, IBinder token, FileDescriptor fd, int portNumber)94     /* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
95             FileDescriptor fd, int portNumber) {
96         mDeviceServer = server;
97         mToken = token;
98         mPortNumber = portNumber;
99         mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(new ParcelFileDescriptor(fd));
100         mThread.start();
101         mGuard.open("close");
102     }
103 
MidiOutputPort(FileDescriptor fd, int portNumber)104     /* package */ MidiOutputPort(FileDescriptor fd, int portNumber) {
105         this(null, null, fd, portNumber);
106     }
107 
108     /**
109      * Returns the port number of this port
110      *
111      * @return the port's port number
112      */
getPortNumber()113     public final int getPortNumber() {
114         return mPortNumber;
115     }
116 
117     @Override
onConnect(MidiReceiver receiver)118     public void onConnect(MidiReceiver receiver) {
119         mDispatcher.getSender().connect(receiver);
120     }
121 
122     @Override
onDisconnect(MidiReceiver receiver)123     public void onDisconnect(MidiReceiver receiver) {
124         mDispatcher.getSender().disconnect(receiver);
125     }
126 
127     @Override
close()128     public void close() throws IOException {
129         synchronized (mGuard) {
130             if (mIsClosed) return;
131 
132             mGuard.close();
133             mInputStream.close();
134             if (mDeviceServer != null) {
135                 try {
136                     mDeviceServer.closePort(mToken);
137                 } catch (RemoteException e) {
138                     Log.e(TAG, "RemoteException in MidiOutputPort.close()");
139                 }
140             }
141             mIsClosed = true;
142         }
143     }
144 
145     @Override
finalize()146     protected void finalize() throws Throwable {
147         try {
148             mGuard.warnIfOpen();
149             // not safe to make binder calls from finalize()
150             mDeviceServer = null;
151             close();
152         } finally {
153             super.finalize();
154         }
155     }
156 }
157