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