UsbMidiDevice.java revision 10024b3dc12a8552c1547b67810c77b865045cc8
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.hardware.usb.UsbDevice;
21import android.midi.MidiDeviceInfo;
22import android.midi.MidiDeviceServer;
23import android.midi.MidiManager;
24import android.midi.MidiReceiver;
25import android.midi.MidiSender;
26import android.midi.MidiUtils;
27import android.os.Bundle;
28import android.system.ErrnoException;
29import android.system.Os;
30import android.system.OsConstants;
31import android.system.StructPollfd;
32import android.util.Log;
33
34import java.io.Closeable;
35import java.io.FileDescriptor;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39
40public final class UsbMidiDevice implements Closeable {
41    private static final String TAG = "UsbMidiDevice";
42
43    private final MidiDeviceServer mServer;
44    private final MidiReceiver[] mOutputPortReceivers;
45
46    // for polling multiple FileDescriptors for MIDI events
47    private final StructPollfd[] mPollFDs;
48    private final FileInputStream[] mInputStreams;
49    private final FileOutputStream[] mOutputStreams;
50
51    public static UsbMidiDevice create(Context context, UsbDevice usbDevice, int card, int device) {
52        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
53        if (midiManager == null) {
54            Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
55            return null;
56        }
57
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        Bundle properties = new Bundle();
73        properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, usbDevice.getManufacturerName());
74        properties.putString(MidiDeviceInfo.PROPERTY_MODEL, usbDevice.getProductName());
75        properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, usbDevice.getSerialNumber());
76        properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
77        properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
78        properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
79        MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
80                false, MidiDeviceInfo.TYPE_USB);
81        if (server == null) {
82            Log.e(TAG, "createDeviceServer failed");
83            return null;
84        }
85
86        return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
87    }
88
89    private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
90            FileDescriptor[] outputFiles) {
91        mServer = server;
92        int inputCount = inputFiles.length;
93        int outputCount = outputFiles.length;
94
95        mPollFDs = new StructPollfd[inputCount];
96        mInputStreams = new FileInputStream[inputCount];
97        for (int i = 0; i < inputCount; i++) {
98            FileDescriptor fd = inputFiles[i];
99            StructPollfd pollfd = new StructPollfd();
100            pollfd.fd = fd;
101            pollfd.events = (short)OsConstants.POLLIN;
102            mPollFDs[i] = pollfd;
103            mInputStreams[i] = new FileInputStream(fd);
104        }
105
106        mOutputStreams = new FileOutputStream[outputCount];
107        for (int i = 0; i < outputCount; i++) {
108            mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
109        }
110
111        mOutputPortReceivers = new MidiReceiver[outputCount];
112        for (int port = 0; port < outputCount; port++) {
113            mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
114        }
115
116        for (int port = 0; port < inputCount; port++) {
117            final int portNumberF = port;
118            MidiReceiver receiver = new MidiReceiver() {
119
120                @Override
121                public void onPost(byte[] data, int offset, int count, long timestamp)
122                        throws IOException {
123                    // FIXME - timestamps are ignored, future posting not supported yet.
124                    mOutputStreams[portNumberF].write(data, offset, count);
125                }
126            };
127            MidiSender sender = server.openInputPortSender(port);
128            sender.connect(receiver);
129        }
130
131        new Thread() {
132            @Override
133            public void run() {
134                byte[] buffer = new byte[3];
135                try {
136                    while (true) {
137                        // look for a readable FileDescriptor
138                        for (int index = 0; index < mPollFDs.length; index++) {
139                            StructPollfd pfd = mPollFDs[index];
140                            if ((pfd.revents & OsConstants.POLLIN) != 0) {
141                                // clear readable flag
142                                pfd.revents = 0;
143                                int count = readMessage(buffer, index);
144                                mOutputPortReceivers[index].onPost(buffer, 0, count, System.nanoTime());
145                            }
146                        }
147
148                        // poll if none are readable
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 int readMessage(byte[] buffer, int index) throws IOException {
173        FileInputStream inputStream = mInputStreams[index];
174
175        if (inputStream.read(buffer, 0, 1) != 1) {
176            Log.e(TAG, "could not read command byte");
177            return -1;
178        }
179        int dataSize = MidiUtils.getMessageDataSize(buffer[0]);
180        if (dataSize < 0) {
181            return -1;
182        }
183        if (dataSize > 0) {
184            if (inputStream.read(buffer, 1, dataSize) != dataSize) {
185                Log.e(TAG, "could not read command data");
186                return -1;
187            }
188        }
189        return dataSize + 1;
190    }
191
192    private static native int nativeGetSubdeviceCount(int card, int device);
193    private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
194}
195