UsbMidiDevice.java revision 2a57bc7fd602853dc1a22dcee1ff50f92cc29060
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
17package com.android.server.usb;
18
19import android.content.Context;
20import android.midi.MidiDeviceInfo;
21import android.midi.MidiDeviceServer;
22import android.midi.MidiManager;
23import android.midi.MidiPort;
24import android.midi.MidiReceiver;
25import android.midi.MidiSender;
26import android.os.Bundle;
27import android.system.ErrnoException;
28import android.system.Os;
29import android.system.OsConstants;
30import android.system.StructPollfd;
31import android.util.Log;
32
33import java.io.Closeable;
34import java.io.FileDescriptor;
35import java.io.FileInputStream;
36import java.io.FileOutputStream;
37import java.io.IOException;
38
39public final class UsbMidiDevice implements Closeable {
40    private static final String TAG = "UsbMidiDevice";
41
42    private final MidiDeviceServer mServer;
43    private final MidiReceiver[] mOutputPortReceivers;
44
45    // for polling multiple FileDescriptors for MIDI events
46    private final StructPollfd[] mPollFDs;
47    // streams for reading from ALSA driver
48    private final FileInputStream[] mInputStreams;
49    // streams for writing to ALSA driver
50    private final FileOutputStream[] mOutputStreams;
51
52    public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
53        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
54        if (midiManager == null) {
55            Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
56            return null;
57        }
58
59        // FIXME - support devices with different number of input and output ports
60        int subDevices = nativeGetSubdeviceCount(card, device);
61        if (subDevices <= 0) {
62            Log.e(TAG, "nativeGetSubdeviceCount failed");
63            return null;
64        }
65
66        // FIXME - support devices with different number of input and output ports
67        FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
68        if (fileDescriptors == null) {
69            Log.e(TAG, "nativeOpen failed");
70            return null;
71        }
72
73        MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
74                false, MidiDeviceInfo.TYPE_USB);
75        if (server == null) {
76            Log.e(TAG, "createDeviceServer failed");
77            return null;
78        }
79
80        return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
81    }
82
83    private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
84            FileDescriptor[] outputFiles) {
85        mServer = server;
86        int inputCount = inputFiles.length;
87        int outputCount = outputFiles.length;
88
89        mPollFDs = new StructPollfd[inputCount];
90        mInputStreams = new FileInputStream[inputCount];
91        for (int i = 0; i < inputCount; i++) {
92            FileDescriptor fd = inputFiles[i];
93            StructPollfd pollfd = new StructPollfd();
94            pollfd.fd = fd;
95            pollfd.events = (short)OsConstants.POLLIN;
96            mPollFDs[i] = pollfd;
97            mInputStreams[i] = new FileInputStream(fd);
98        }
99
100        mOutputStreams = new FileOutputStream[outputCount];
101        for (int i = 0; i < outputCount; i++) {
102            mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
103        }
104
105        mOutputPortReceivers = new MidiReceiver[outputCount];
106        for (int port = 0; port < outputCount; port++) {
107            mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
108        }
109
110        for (int port = 0; port < inputCount; port++) {
111            final int portNumberF = port;
112            MidiReceiver receiver = new MidiReceiver() {
113
114                @Override
115                public void onPost(byte[] data, int offset, int count, long timestamp)
116                        throws IOException {
117                    // FIXME - timestamps are ignored, future posting not supported yet.
118                    mOutputStreams[portNumberF].write(data, offset, count);
119                }
120            };
121            MidiSender sender = server.openInputPortSender(port);
122            sender.connect(receiver);
123        }
124
125        new Thread() {
126            @Override
127            public void run() {
128                byte[] buffer = new byte[MidiPort.MAX_PACKET_DATA_SIZE];
129                try {
130                    boolean done = false;
131                    while (!done) {
132                        // look for a readable FileDescriptor
133                        for (int index = 0; index < mPollFDs.length; index++) {
134                            StructPollfd pfd = mPollFDs[index];
135                            if ((pfd.revents & OsConstants.POLLIN) != 0) {
136                                // clear readable flag
137                                pfd.revents = 0;
138
139                                int count = mInputStreams[index].read(buffer);
140                                mOutputPortReceivers[index].onPost(buffer, 0, count,
141                                        System.nanoTime());
142                            } else if ((pfd.revents & (OsConstants.POLLERR
143                                                        | OsConstants.POLLHUP)) != 0) {
144                                done = true;
145                            }
146                        }
147
148                        // wait until we have a readable port
149                        Os.poll(mPollFDs, -1 /* infinite timeout */);
150                     }
151                } catch (IOException e) {
152                    Log.d(TAG, "reader thread exiting");
153                } catch (ErrnoException e) {
154                    Log.d(TAG, "reader thread exiting");
155                }
156            }
157        }.start();
158    }
159
160    @Override
161    public void close() throws IOException {
162        mServer.close();
163
164        for (int i = 0; i < mInputStreams.length; i++) {
165            mInputStreams[i].close();
166        }
167        for (int i = 0; i < mOutputStreams.length; i++) {
168            mOutputStreams[i].close();
169        }
170    }
171
172    private static native int nativeGetSubdeviceCount(int card, int device);
173    private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
174}
175