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