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