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 output port
     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 
    132     public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
    133         // FIXME - support devices with different number of input and output ports
    134         int subDeviceCount = nativeGetSubdeviceCount(card, device);
    135         if (subDeviceCount <= 0) {
    136             Log.e(TAG, "nativeGetSubdeviceCount failed");
    137             return null;
    138         }
    139 
    140         UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
    141         if (!midiDevice.register(context, properties)) {
    142             IoUtils.closeQuietly(midiDevice);
    143             Log.e(TAG, "createDeviceServer failed");
    144             return null;
    145         }
    146         return midiDevice;
    147     }
    148 
    149     private UsbMidiDevice(int card, int device, int subdeviceCount) {
    150         mAlsaCard = card;
    151         mAlsaDevice = device;
    152         mSubdeviceCount = subdeviceCount;
    153 
    154         // FIXME - support devices with different number of input and output ports
    155         int inputCount = subdeviceCount;
    156         mInputPortReceivers = new InputReceiverProxy[inputCount];
    157         for (int port = 0; port < inputCount; port++) {
    158             mInputPortReceivers[port] = new InputReceiverProxy();
    159         }
    160     }
    161 
    162     private boolean openLocked() {
    163         // FIXME - support devices with different number of input and output ports
    164         FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount);
    165         if (fileDescriptors == null) {
    166             Log.e(TAG, "nativeOpen failed");
    167             return false;
    168         }
    169 
    170         mFileDescriptors = fileDescriptors;
    171         int inputCount = fileDescriptors.length;
    172         // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll()
    173         // in our input thread
    174         int outputCount = fileDescriptors.length - 1;
    175 
    176         mPollFDs = new StructPollfd[inputCount];
    177         mInputStreams = new FileInputStream[inputCount];
    178         for (int i = 0; i < inputCount; i++) {
    179             FileDescriptor fd = fileDescriptors[i];
    180             StructPollfd pollfd = new StructPollfd();
    181             pollfd.fd = fd;
    182             pollfd.events = (short)OsConstants.POLLIN;
    183             mPollFDs[i] = pollfd;
    184             mInputStreams[i] = new FileInputStream(fd);
    185         }
    186 
    187         mOutputStreams = new FileOutputStream[outputCount];
    188         mEventSchedulers = new MidiEventScheduler[outputCount];
    189         for (int i = 0; i < outputCount; i++) {
    190             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
    191 
    192             MidiEventScheduler scheduler = new MidiEventScheduler();
    193             mEventSchedulers[i] = scheduler;
    194             mInputPortReceivers[i].setReceiver(scheduler.getReceiver());
    195         }
    196 
    197         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
    198 
    199         // Create input thread which will read from all input ports
    200         new Thread("UsbMidiDevice input thread") {
    201             @Override
    202             public void run() {
    203                 byte[] buffer = new byte[BUFFER_SIZE];
    204                 try {
    205                     while (true) {
    206                         // Record time of event immediately after waking.
    207                         long timestamp = System.nanoTime();
    208                         synchronized (mLock) {
    209                             if (!mIsOpen) break;
    210 
    211                             // look for a readable FileDescriptor
    212                             for (int index = 0; index < mPollFDs.length; index++) {
    213                                 StructPollfd pfd = mPollFDs[index];
    214                                 if ((pfd.revents & (OsConstants.POLLERR
    215                                                             | OsConstants.POLLHUP)) != 0) {
    216                                     break;
    217                                 } else if ((pfd.revents & OsConstants.POLLIN) != 0) {
    218                                     // clear readable flag
    219                                     pfd.revents = 0;
    220 
    221                                     if (index == mInputStreams.length - 1) {
    222                                         // last file descriptor is used only for unblocking Os.poll()
    223                                         break;
    224                                     }
    225 
    226                                     int count = mInputStreams[index].read(buffer);
    227                                     outputReceivers[index].send(buffer, 0, count, timestamp);
    228                                 }
    229                             }
    230                         }
    231 
    232                         // wait until we have a readable port or we are signalled to close
    233                         Os.poll(mPollFDs, -1 /* infinite timeout */);
    234                      }
    235                 } catch (IOException e) {
    236                     Log.d(TAG, "reader thread exiting");
    237                 } catch (ErrnoException e) {
    238                     Log.d(TAG, "reader thread exiting");
    239                 }
    240                 Log.d(TAG, "input thread exit");
    241             }
    242         }.start();
    243 
    244         // Create output thread for each output port
    245         for (int port = 0; port < outputCount; port++) {
    246             final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
    247             final FileOutputStream outputStreamF = mOutputStreams[port];
    248             final int portF = port;
    249 
    250             new Thread("UsbMidiDevice output thread " + port) {
    251                 @Override
    252                 public void run() {
    253                     while (true) {
    254                         MidiEvent event;
    255                         try {
    256                             event = (MidiEvent)eventSchedulerF.waitNextEvent();
    257                         } catch (InterruptedException e) {
    258                             // try again
    259                             continue;
    260                         }
    261                         if (event == null) {
    262                             break;
    263                         }
    264                         try {
    265                             outputStreamF.write(event.data, 0, event.count);
    266                         } catch (IOException e) {
    267                             Log.e(TAG, "write failed for port " + portF);
    268                         }
    269                         eventSchedulerF.addEventToPool(event);
    270                     }
    271                     Log.d(TAG, "output thread exit");
    272                 }
    273             }.start();
    274         }
    275 
    276         mIsOpen = true;
    277         return true;
    278     }
    279 
    280     private boolean register(Context context, Bundle properties) {
    281         MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
    282         if (midiManager == null) {
    283             Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
    284             return false;
    285         }
    286 
    287         mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
    288                 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
    289         if (mServer == null) {
    290             return false;
    291         }
    292 
    293         return true;
    294     }
    295 
    296     @Override
    297     public void close() throws IOException {
    298         synchronized (mLock) {
    299             if (mIsOpen) {
    300                 closeLocked();
    301             }
    302         }
    303 
    304         if (mServer != null) {
    305             IoUtils.closeQuietly(mServer);
    306         }
    307     }
    308 
    309     private void closeLocked() {
    310         for (int i = 0; i < mEventSchedulers.length; i++) {
    311             mInputPortReceivers[i].setReceiver(null);
    312             mEventSchedulers[i].close();
    313         }
    314         mEventSchedulers = null;
    315 
    316         for (int i = 0; i < mInputStreams.length; i++) {
    317             IoUtils.closeQuietly(mInputStreams[i]);
    318         }
    319         mInputStreams = null;
    320 
    321         for (int i = 0; i < mOutputStreams.length; i++) {
    322             IoUtils.closeQuietly(mOutputStreams[i]);
    323         }
    324         mOutputStreams = null;
    325 
    326         // nativeClose will close the file descriptors and signal the input thread to exit
    327         nativeClose(mFileDescriptors);
    328         mFileDescriptors = null;
    329 
    330         mIsOpen = false;
    331     }
    332 
    333     private static native int nativeGetSubdeviceCount(int card, int device);
    334     private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
    335     private native void nativeClose(FileDescriptor[] fileDescriptors);
    336 }
    337