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