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