• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 input port of the physical device
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         @Override
onFlush()132         public void onFlush() throws IOException {
133             MidiReceiver receiver = mReceiver;
134             if (receiver != null) {
135                 receiver.flush();
136             }
137         }
138     }
139 
create(Context context, Bundle properties, int card, int device)140     public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
141         // FIXME - support devices with different number of input and output ports
142         int subDeviceCount = nativeGetSubdeviceCount(card, device);
143         if (subDeviceCount <= 0) {
144             Log.e(TAG, "nativeGetSubdeviceCount failed");
145             return null;
146         }
147 
148         UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
149         if (!midiDevice.register(context, properties)) {
150             IoUtils.closeQuietly(midiDevice);
151             Log.e(TAG, "createDeviceServer failed");
152             return null;
153         }
154         return midiDevice;
155     }
156 
UsbMidiDevice(int card, int device, int subdeviceCount)157     private UsbMidiDevice(int card, int device, int subdeviceCount) {
158         mAlsaCard = card;
159         mAlsaDevice = device;
160         mSubdeviceCount = subdeviceCount;
161 
162         // FIXME - support devices with different number of input and output ports
163         int inputPortCount = subdeviceCount;
164         mInputPortReceivers = new InputReceiverProxy[inputPortCount];
165         for (int port = 0; port < inputPortCount; port++) {
166             mInputPortReceivers[port] = new InputReceiverProxy();
167         }
168     }
169 
openLocked()170     private boolean openLocked() {
171         // FIXME - support devices with different number of input and output ports
172         FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount);
173         if (fileDescriptors == null) {
174             Log.e(TAG, "nativeOpen failed");
175             return false;
176         }
177 
178         mFileDescriptors = fileDescriptors;
179         int inputStreamCount = fileDescriptors.length;
180         // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
181         // in our input thread
182         int outputStreamCount = fileDescriptors.length - 1;
183 
184         mPollFDs = new StructPollfd[inputStreamCount];
185         mInputStreams = new FileInputStream[inputStreamCount];
186         for (int i = 0; i < inputStreamCount; i++) {
187             FileDescriptor fd = fileDescriptors[i];
188             StructPollfd pollfd = new StructPollfd();
189             pollfd.fd = fd;
190             pollfd.events = (short)OsConstants.POLLIN;
191             mPollFDs[i] = pollfd;
192             mInputStreams[i] = new FileInputStream(fd);
193         }
194 
195         mOutputStreams = new FileOutputStream[outputStreamCount];
196         mEventSchedulers = new MidiEventScheduler[outputStreamCount];
197         for (int i = 0; i < outputStreamCount; i++) {
198             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
199 
200             MidiEventScheduler scheduler = new MidiEventScheduler();
201             mEventSchedulers[i] = scheduler;
202             mInputPortReceivers[i].setReceiver(scheduler.getReceiver());
203         }
204 
205         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
206 
207         // Create input thread which will read from all output ports of the physical device
208         new Thread("UsbMidiDevice input thread") {
209             @Override
210             public void run() {
211                 byte[] buffer = new byte[BUFFER_SIZE];
212                 try {
213                     while (true) {
214                         // Record time of event immediately after waking.
215                         long timestamp = System.nanoTime();
216                         synchronized (mLock) {
217                             if (!mIsOpen) break;
218 
219                             // look for a readable FileDescriptor
220                             for (int index = 0; index < mPollFDs.length; index++) {
221                                 StructPollfd pfd = mPollFDs[index];
222                                 if ((pfd.revents & (OsConstants.POLLERR
223                                                             | OsConstants.POLLHUP)) != 0) {
224                                     break;
225                                 } else if ((pfd.revents & OsConstants.POLLIN) != 0) {
226                                     // clear readable flag
227                                     pfd.revents = 0;
228 
229                                     if (index == mInputStreams.length - 1) {
230                                         // last file descriptor is used only for unblocking Os.poll()
231                                         break;
232                                     }
233 
234                                     int count = mInputStreams[index].read(buffer);
235                                     outputReceivers[index].send(buffer, 0, count, timestamp);
236                                 }
237                             }
238                         }
239 
240                         // wait until we have a readable port or we are signalled to close
241                         Os.poll(mPollFDs, -1 /* infinite timeout */);
242                      }
243                 } catch (IOException e) {
244                     Log.d(TAG, "reader thread exiting");
245                 } catch (ErrnoException e) {
246                     Log.d(TAG, "reader thread exiting");
247                 }
248                 Log.d(TAG, "input thread exit");
249             }
250         }.start();
251 
252         // Create output thread for each input port of the physical device
253         for (int port = 0; port < outputStreamCount; port++) {
254             final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
255             final FileOutputStream outputStreamF = mOutputStreams[port];
256             final int portF = port;
257 
258             new Thread("UsbMidiDevice output thread " + port) {
259                 @Override
260                 public void run() {
261                     while (true) {
262                         MidiEvent event;
263                         try {
264                             event = (MidiEvent)eventSchedulerF.waitNextEvent();
265                         } catch (InterruptedException e) {
266                             // try again
267                             continue;
268                         }
269                         if (event == null) {
270                             break;
271                         }
272                         try {
273                             outputStreamF.write(event.data, 0, event.count);
274                         } catch (IOException e) {
275                             Log.e(TAG, "write failed for port " + portF);
276                         }
277                         eventSchedulerF.addEventToPool(event);
278                     }
279                     Log.d(TAG, "output thread exit");
280                 }
281             }.start();
282         }
283 
284         mIsOpen = true;
285         return true;
286     }
287 
register(Context context, Bundle properties)288     private boolean register(Context context, Bundle properties) {
289         MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
290         if (midiManager == null) {
291             Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
292             return false;
293         }
294 
295         mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
296                 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
297         if (mServer == null) {
298             return false;
299         }
300 
301         return true;
302     }
303 
304     @Override
close()305     public void close() throws IOException {
306         synchronized (mLock) {
307             if (mIsOpen) {
308                 closeLocked();
309             }
310         }
311 
312         if (mServer != null) {
313             IoUtils.closeQuietly(mServer);
314         }
315     }
316 
closeLocked()317     private void closeLocked() {
318         for (int i = 0; i < mEventSchedulers.length; i++) {
319             mInputPortReceivers[i].setReceiver(null);
320             mEventSchedulers[i].close();
321         }
322         mEventSchedulers = null;
323 
324         for (int i = 0; i < mInputStreams.length; i++) {
325             IoUtils.closeQuietly(mInputStreams[i]);
326         }
327         mInputStreams = null;
328 
329         for (int i = 0; i < mOutputStreams.length; i++) {
330             IoUtils.closeQuietly(mOutputStreams[i]);
331         }
332         mOutputStreams = null;
333 
334         // nativeClose will close the file descriptors and signal the input thread to exit
335         nativeClose(mFileDescriptors);
336         mFileDescriptors = null;
337 
338         mIsOpen = false;
339     }
340 
nativeGetSubdeviceCount(int card, int device)341     private static native int nativeGetSubdeviceCount(int card, int device);
nativeOpen(int card, int device, int subdeviceCount)342     private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
nativeClose(FileDescriptor[] fileDescriptors)343     private native void nativeClose(FileDescriptor[] fileDescriptors);
344 }
345