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 an
14  * limitations under the License.
15  */
16 
17 package com.android.server.usb;
18 
19 import android.content.Context;
20 import android.media.midi.MidiDeviceInfo;
21 import android.media.midi.MidiDeviceServer;
22 import android.media.midi.MidiDeviceStatus;
23 import android.media.midi.MidiManager;
24 import android.media.midi.MidiReceiver;
25 import android.media.midi.MidiSender;
26 import android.os.Bundle;
27 import android.system.ErrnoException;
28 import android.system.Os;
29 import android.system.OsConstants;
30 import android.system.StructPollfd;
31 import android.util.Log;
32 
33 import com.android.internal.midi.MidiEventScheduler;
34 import com.android.internal.midi.MidiEventScheduler.MidiEvent;
35 
36 import libcore.io.IoUtils;
37 
38 import java.io.Closeable;
39 import java.io.FileDescriptor;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 
44 public final class UsbMidiDevice implements Closeable {
45     private static final String TAG = "UsbMidiDevice";
46 
47     private final int mAlsaCard;
48     private final int mAlsaDevice;
49     private final int mSubdeviceCount;
50     private final InputReceiverProxy[] mInputPortReceivers;
51 
52     private MidiDeviceServer mServer;
53 
54     // event schedulers for each output port
55     private MidiEventScheduler[] mEventSchedulers;
56 
57     private static final int BUFFER_SIZE = 512;
58 
59     private FileDescriptor[] mFileDescriptors;
60 
61     // for polling multiple FileDescriptors for MIDI events
62     private StructPollfd[] mPollFDs;
63     // streams for reading from ALSA driver
64     private FileInputStream[] mInputStreams;
65     // streams for writing to ALSA driver
66     private FileOutputStream[] mOutputStreams;
67 
68     private final Object mLock = new Object();
69     private boolean mIsOpen;
70 
71     // pipe file descriptor for signalling input thread to exit
72     // only accessed from JNI code
73     private int mPipeFD = -1;
74 
75     private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
76 
77         @Override
78         public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
79             MidiDeviceInfo deviceInfo = status.getDeviceInfo();
80             int inputPorts = deviceInfo.getInputPortCount();
81             int outputPorts = deviceInfo.getOutputPortCount();
82             boolean hasOpenPorts = false;
83 
84             for (int i = 0; i < inputPorts; i++) {
85                 if (status.isInputPortOpen(i)) {
86                     hasOpenPorts = true;
87                     break;
88                 }
89             }
90 
91             if (!hasOpenPorts) {
92                 for (int i = 0; i < outputPorts; i++) {
93                     if (status.getOutputPortOpenCount(i) > 0) {
94                         hasOpenPorts = true;
95                         break;
96                     }
97                 }
98             }
99 
100             synchronized (mLock) {
101                 if (hasOpenPorts && !mIsOpen) {
102                     openLocked();
103                 } else if (!hasOpenPorts && mIsOpen) {
104                     closeLocked();
105                 }
106             }
107         }
108 
109         @Override
110         public void onClose() {
111         }
112     };
113 
114     // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
115     // until the device has active clients
116     private final class InputReceiverProxy extends MidiReceiver {
117         private MidiReceiver mReceiver;
118 
119         @Override
onSend(byte[] msg, int offset, int count, long timestamp)120         public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
121             MidiReceiver receiver = mReceiver;
122             if (receiver != null) {
123                 receiver.send(msg, offset, count, timestamp);
124             }
125         }
126 
setReceiver(MidiReceiver receiver)127         public void setReceiver(MidiReceiver receiver) {
128             mReceiver = receiver;
129         }
130     }
131 
create(Context context, Bundle properties, int card, int device)132     public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
133         // FIXME - support devices with different number of input and output ports
134         int subDeviceCount = nativeGetSubdeviceCount(card, device);
135         if (subDeviceCount <= 0) {
136             Log.e(TAG, "nativeGetSubdeviceCount failed");
137             return null;
138         }
139 
140         UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
141         if (!midiDevice.register(context, properties)) {
142             IoUtils.closeQuietly(midiDevice);
143             Log.e(TAG, "createDeviceServer failed");
144             return null;
145         }
146         return midiDevice;
147     }
148 
UsbMidiDevice(int card, int device, int subdeviceCount)149     private UsbMidiDevice(int card, int device, int subdeviceCount) {
150         mAlsaCard = card;
151         mAlsaDevice = device;
152         mSubdeviceCount = subdeviceCount;
153 
154         // FIXME - support devices with different number of input and output ports
155         int inputCount = subdeviceCount;
156         mInputPortReceivers = new InputReceiverProxy[inputCount];
157         for (int port = 0; port < inputCount; port++) {
158             mInputPortReceivers[port] = new InputReceiverProxy();
159         }
160     }
161 
openLocked()162     private boolean openLocked() {
163         // FIXME - support devices with different number of input and output ports
164         FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount);
165         if (fileDescriptors == null) {
166             Log.e(TAG, "nativeOpen failed");
167             return false;
168         }
169 
170         mFileDescriptors = fileDescriptors;
171         int inputCount = fileDescriptors.length;
172         // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
173         // in our input thread
174         int outputCount = fileDescriptors.length - 1;
175 
176         mPollFDs = new StructPollfd[inputCount];
177         mInputStreams = new FileInputStream[inputCount];
178         for (int i = 0; i < inputCount; i++) {
179             FileDescriptor fd = fileDescriptors[i];
180             StructPollfd pollfd = new StructPollfd();
181             pollfd.fd = fd;
182             pollfd.events = (short)OsConstants.POLLIN;
183             mPollFDs[i] = pollfd;
184             mInputStreams[i] = new FileInputStream(fd);
185         }
186 
187         mOutputStreams = new FileOutputStream[outputCount];
188         mEventSchedulers = new MidiEventScheduler[outputCount];
189         for (int i = 0; i < outputCount; i++) {
190             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
191 
192             MidiEventScheduler scheduler = new MidiEventScheduler();
193             mEventSchedulers[i] = scheduler;
194             mInputPortReceivers[i].setReceiver(scheduler.getReceiver());
195         }
196 
197         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
198 
199         // Create input thread which will read from all input ports
200         new Thread("UsbMidiDevice input thread") {
201             @Override
202             public void run() {
203                 byte[] buffer = new byte[BUFFER_SIZE];
204                 try {
205                     while (true) {
206                         // Record time of event immediately after waking.
207                         long timestamp = System.nanoTime();
208                         synchronized (mLock) {
209                             if (!mIsOpen) break;
210 
211                             // look for a readable FileDescriptor
212                             for (int index = 0; index < mPollFDs.length; index++) {
213                                 StructPollfd pfd = mPollFDs[index];
214                                 if ((pfd.revents & (OsConstants.POLLERR
215                                                             | OsConstants.POLLHUP)) != 0) {
216                                     break;
217                                 } else if ((pfd.revents & OsConstants.POLLIN) != 0) {
218                                     // clear readable flag
219                                     pfd.revents = 0;
220 
221                                     if (index == mInputStreams.length - 1) {
222                                         // last file descriptor is used only for unblocking Os.poll()
223                                         break;
224                                     }
225 
226                                     int count = mInputStreams[index].read(buffer);
227                                     outputReceivers[index].send(buffer, 0, count, timestamp);
228                                 }
229                             }
230                         }
231 
232                         // wait until we have a readable port or we are signalled to close
233                         Os.poll(mPollFDs, -1 /* infinite timeout */);
234                      }
235                 } catch (IOException e) {
236                     Log.d(TAG, "reader thread exiting");
237                 } catch (ErrnoException e) {
238                     Log.d(TAG, "reader thread exiting");
239                 }
240                 Log.d(TAG, "input thread exit");
241             }
242         }.start();
243 
244         // Create output thread for each output port
245         for (int port = 0; port < outputCount; port++) {
246             final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
247             final FileOutputStream outputStreamF = mOutputStreams[port];
248             final int portF = port;
249 
250             new Thread("UsbMidiDevice output thread " + port) {
251                 @Override
252                 public void run() {
253                     while (true) {
254                         MidiEvent event;
255                         try {
256                             event = (MidiEvent)eventSchedulerF.waitNextEvent();
257                         } catch (InterruptedException e) {
258                             // try again
259                             continue;
260                         }
261                         if (event == null) {
262                             break;
263                         }
264                         try {
265                             outputStreamF.write(event.data, 0, event.count);
266                         } catch (IOException e) {
267                             Log.e(TAG, "write failed for port " + portF);
268                         }
269                         eventSchedulerF.addEventToPool(event);
270                     }
271                     Log.d(TAG, "output thread exit");
272                 }
273             }.start();
274         }
275 
276         mIsOpen = true;
277         return true;
278     }
279 
register(Context context, Bundle properties)280     private boolean register(Context context, Bundle properties) {
281         MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
282         if (midiManager == null) {
283             Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
284             return false;
285         }
286 
287         mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
288                 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
289         if (mServer == null) {
290             return false;
291         }
292 
293         return true;
294     }
295 
296     @Override
close()297     public void close() throws IOException {
298         synchronized (mLock) {
299             if (mIsOpen) {
300                 closeLocked();
301             }
302         }
303 
304         if (mServer != null) {
305             IoUtils.closeQuietly(mServer);
306         }
307     }
308 
closeLocked()309     private void closeLocked() {
310         for (int i = 0; i < mEventSchedulers.length; i++) {
311             mInputPortReceivers[i].setReceiver(null);
312             mEventSchedulers[i].close();
313         }
314         mEventSchedulers = null;
315 
316         for (int i = 0; i < mInputStreams.length; i++) {
317             IoUtils.closeQuietly(mInputStreams[i]);
318         }
319         mInputStreams = null;
320 
321         for (int i = 0; i < mOutputStreams.length; i++) {
322             IoUtils.closeQuietly(mOutputStreams[i]);
323         }
324         mOutputStreams = null;
325 
326         // nativeClose will close the file descriptors and signal the input thread to exit
327         nativeClose(mFileDescriptors);
328         mFileDescriptors = null;
329 
330         mIsOpen = false;
331     }
332 
nativeGetSubdeviceCount(int card, int device)333     private static native int nativeGetSubdeviceCount(int card, int device);
nativeOpen(int card, int device, int subdeviceCount)334     private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
nativeClose(FileDescriptor[] fileDescriptors)335     private native void nativeClose(FileDescriptor[] fileDescriptors);
336 }
337