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 17 package com.android.server.usb; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.media.midi.MidiDeviceInfo; 22 import android.media.midi.MidiDeviceServer; 23 import android.media.midi.MidiDeviceStatus; 24 import android.media.midi.MidiManager; 25 import android.media.midi.MidiReceiver; 26 import android.os.Bundle; 27 import android.service.usb.UsbMidiDeviceProto; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.system.OsConstants; 31 import android.system.StructPollfd; 32 import android.util.Log; 33 34 import com.android.internal.midi.MidiEventScheduler; 35 import com.android.internal.midi.MidiEventScheduler.MidiEvent; 36 import com.android.internal.util.dump.DualDumpOutputStream; 37 38 import libcore.io.IoUtils; 39 40 import java.io.Closeable; 41 import java.io.FileDescriptor; 42 import java.io.FileInputStream; 43 import java.io.FileOutputStream; 44 import java.io.IOException; 45 46 public final class UsbMidiDevice implements Closeable { 47 private static final String TAG = "UsbMidiDevice"; 48 49 private final int mAlsaCard; 50 private final int mAlsaDevice; 51 private final int mSubdeviceCount; 52 private final InputReceiverProxy[] mInputPortReceivers; 53 54 private MidiDeviceServer mServer; 55 56 // event schedulers for each input port of the physical device 57 private MidiEventScheduler[] mEventSchedulers; 58 59 private static final int BUFFER_SIZE = 512; 60 61 private FileDescriptor[] mFileDescriptors; 62 63 // for polling multiple FileDescriptors for MIDI events 64 private StructPollfd[] mPollFDs; 65 // streams for reading from ALSA driver 66 private FileInputStream[] mInputStreams; 67 // streams for writing to ALSA driver 68 private FileOutputStream[] mOutputStreams; 69 70 private final Object mLock = new Object(); 71 private boolean mIsOpen; 72 73 // pipe file descriptor for signalling input thread to exit 74 // only accessed from JNI code 75 private int mPipeFD = -1; 76 77 private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { 78 79 @Override 80 public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { 81 MidiDeviceInfo deviceInfo = status.getDeviceInfo(); 82 int inputPorts = deviceInfo.getInputPortCount(); 83 int outputPorts = deviceInfo.getOutputPortCount(); 84 boolean hasOpenPorts = false; 85 86 for (int i = 0; i < inputPorts; i++) { 87 if (status.isInputPortOpen(i)) { 88 hasOpenPorts = true; 89 break; 90 } 91 } 92 93 if (!hasOpenPorts) { 94 for (int i = 0; i < outputPorts; i++) { 95 if (status.getOutputPortOpenCount(i) > 0) { 96 hasOpenPorts = true; 97 break; 98 } 99 } 100 } 101 102 synchronized (mLock) { 103 if (hasOpenPorts && !mIsOpen) { 104 openLocked(); 105 } else if (!hasOpenPorts && mIsOpen) { 106 closeLocked(); 107 } 108 } 109 } 110 111 @Override 112 public void onClose() { 113 } 114 }; 115 116 // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist 117 // until the device has active clients 118 private final class InputReceiverProxy extends MidiReceiver { 119 private MidiReceiver mReceiver; 120 121 @Override onSend(byte[] msg, int offset, int count, long timestamp)122 public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { 123 MidiReceiver receiver = mReceiver; 124 if (receiver != null) { 125 receiver.send(msg, offset, count, timestamp); 126 } 127 } 128 setReceiver(MidiReceiver receiver)129 public void setReceiver(MidiReceiver receiver) { 130 mReceiver = receiver; 131 } 132 133 @Override onFlush()134 public void onFlush() throws IOException { 135 MidiReceiver receiver = mReceiver; 136 if (receiver != null) { 137 receiver.flush(); 138 } 139 } 140 } 141 create(Context context, Bundle properties, int card, int device)142 public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { 143 // FIXME - support devices with different number of input and output ports 144 int subDeviceCount = nativeGetSubdeviceCount(card, device); 145 if (subDeviceCount <= 0) { 146 Log.e(TAG, "nativeGetSubdeviceCount failed"); 147 return null; 148 } 149 150 UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); 151 if (!midiDevice.register(context, properties)) { 152 IoUtils.closeQuietly(midiDevice); 153 Log.e(TAG, "createDeviceServer failed"); 154 return null; 155 } 156 return midiDevice; 157 } 158 UsbMidiDevice(int card, int device, int subdeviceCount)159 private UsbMidiDevice(int card, int device, int subdeviceCount) { 160 mAlsaCard = card; 161 mAlsaDevice = device; 162 mSubdeviceCount = subdeviceCount; 163 164 // FIXME - support devices with different number of input and output ports 165 int inputPortCount = subdeviceCount; 166 mInputPortReceivers = new InputReceiverProxy[inputPortCount]; 167 for (int port = 0; port < inputPortCount; port++) { 168 mInputPortReceivers[port] = new InputReceiverProxy(); 169 } 170 } 171 openLocked()172 private boolean openLocked() { 173 // FIXME - support devices with different number of input and output ports 174 FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); 175 if (fileDescriptors == null) { 176 Log.e(TAG, "nativeOpen failed"); 177 return false; 178 } 179 180 mFileDescriptors = fileDescriptors; 181 int inputStreamCount = fileDescriptors.length; 182 // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() 183 // in our input thread 184 int outputStreamCount = fileDescriptors.length - 1; 185 186 mPollFDs = new StructPollfd[inputStreamCount]; 187 mInputStreams = new FileInputStream[inputStreamCount]; 188 for (int i = 0; i < inputStreamCount; i++) { 189 FileDescriptor fd = fileDescriptors[i]; 190 StructPollfd pollfd = new StructPollfd(); 191 pollfd.fd = fd; 192 pollfd.events = (short)OsConstants.POLLIN; 193 mPollFDs[i] = pollfd; 194 mInputStreams[i] = new FileInputStream(fd); 195 } 196 197 mOutputStreams = new FileOutputStream[outputStreamCount]; 198 mEventSchedulers = new MidiEventScheduler[outputStreamCount]; 199 for (int i = 0; i < outputStreamCount; i++) { 200 mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); 201 202 MidiEventScheduler scheduler = new MidiEventScheduler(); 203 mEventSchedulers[i] = scheduler; 204 mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); 205 } 206 207 final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 208 209 // Create input thread which will read from all output ports of the physical device 210 new Thread("UsbMidiDevice input thread") { 211 @Override 212 public void run() { 213 byte[] buffer = new byte[BUFFER_SIZE]; 214 try { 215 while (true) { 216 // Record time of event immediately after waking. 217 long timestamp = System.nanoTime(); 218 synchronized (mLock) { 219 if (!mIsOpen) break; 220 221 // look for a readable FileDescriptor 222 for (int index = 0; index < mPollFDs.length; index++) { 223 StructPollfd pfd = mPollFDs[index]; 224 if ((pfd.revents & (OsConstants.POLLERR 225 | OsConstants.POLLHUP)) != 0) { 226 break; 227 } else if ((pfd.revents & OsConstants.POLLIN) != 0) { 228 // clear readable flag 229 pfd.revents = 0; 230 231 if (index == mInputStreams.length - 1) { 232 // last file descriptor is used only for unblocking Os.poll() 233 break; 234 } 235 236 int count = mInputStreams[index].read(buffer); 237 outputReceivers[index].send(buffer, 0, count, timestamp); 238 } 239 } 240 } 241 242 // wait until we have a readable port or we are signalled to close 243 Os.poll(mPollFDs, -1 /* infinite timeout */); 244 } 245 } catch (IOException e) { 246 Log.d(TAG, "reader thread exiting"); 247 } catch (ErrnoException e) { 248 Log.d(TAG, "reader thread exiting"); 249 } 250 Log.d(TAG, "input thread exit"); 251 } 252 }.start(); 253 254 // Create output thread for each input port of the physical device 255 for (int port = 0; port < outputStreamCount; port++) { 256 final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; 257 final FileOutputStream outputStreamF = mOutputStreams[port]; 258 final int portF = port; 259 260 new Thread("UsbMidiDevice output thread " + port) { 261 @Override 262 public void run() { 263 while (true) { 264 MidiEvent event; 265 try { 266 event = (MidiEvent)eventSchedulerF.waitNextEvent(); 267 } catch (InterruptedException e) { 268 // try again 269 continue; 270 } 271 if (event == null) { 272 break; 273 } 274 try { 275 outputStreamF.write(event.data, 0, event.count); 276 } catch (IOException e) { 277 Log.e(TAG, "write failed for port " + portF); 278 } 279 eventSchedulerF.addEventToPool(event); 280 } 281 Log.d(TAG, "output thread exit"); 282 } 283 }.start(); 284 } 285 286 mIsOpen = true; 287 return true; 288 } 289 register(Context context, Bundle properties)290 private boolean register(Context context, Bundle properties) { 291 MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 292 if (midiManager == null) { 293 Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); 294 return false; 295 } 296 297 mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, 298 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); 299 if (mServer == null) { 300 return false; 301 } 302 303 return true; 304 } 305 306 @Override close()307 public void close() throws IOException { 308 synchronized (mLock) { 309 if (mIsOpen) { 310 closeLocked(); 311 } 312 } 313 314 if (mServer != null) { 315 IoUtils.closeQuietly(mServer); 316 } 317 } 318 closeLocked()319 private void closeLocked() { 320 for (int i = 0; i < mEventSchedulers.length; i++) { 321 mInputPortReceivers[i].setReceiver(null); 322 mEventSchedulers[i].close(); 323 } 324 mEventSchedulers = null; 325 326 for (int i = 0; i < mInputStreams.length; i++) { 327 IoUtils.closeQuietly(mInputStreams[i]); 328 } 329 mInputStreams = null; 330 331 for (int i = 0; i < mOutputStreams.length; i++) { 332 IoUtils.closeQuietly(mOutputStreams[i]); 333 } 334 mOutputStreams = null; 335 336 // nativeClose will close the file descriptors and signal the input thread to exit 337 nativeClose(mFileDescriptors); 338 mFileDescriptors = null; 339 340 mIsOpen = false; 341 } 342 343 /** 344 * Write a description of the device to a dump stream. 345 */ dump(String deviceAddr, @NonNull DualDumpOutputStream dump, @NonNull String idName, long id)346 public void dump(String deviceAddr, @NonNull DualDumpOutputStream dump, @NonNull String idName, 347 long id) { 348 long token = dump.start(idName, id); 349 350 dump.write("device_address", UsbMidiDeviceProto.DEVICE_ADDRESS, deviceAddr); 351 dump.write("card", UsbMidiDeviceProto.CARD, mAlsaCard); 352 dump.write("device", UsbMidiDeviceProto.DEVICE, mAlsaDevice); 353 354 dump.end(token); 355 } 356 nativeGetSubdeviceCount(int card, int device)357 private static native int nativeGetSubdeviceCount(int card, int device); nativeOpen(int card, int device, int subdeviceCount)358 private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); nativeClose(FileDescriptor[] fileDescriptors)359 private native void nativeClose(FileDescriptor[] fileDescriptors); 360 } 361