Home | History | Annotate | Download | only in midi
      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 and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media.midi;
     18 
     19 import android.os.Binder;
     20 import android.os.IBinder;
     21 import android.os.Process;
     22 import android.os.RemoteException;
     23 import android.util.Log;
     24 
     25 import dalvik.system.CloseGuard;
     26 
     27 import libcore.io.IoUtils;
     28 
     29 import java.io.Closeable;
     30 import java.io.FileDescriptor;
     31 import java.io.IOException;
     32 
     33 import java.util.HashSet;
     34 
     35 /**
     36  * This class is used for sending and receiving data to and from a MIDI device
     37  * Instances of this class are created by {@link MidiManager#openDevice}.
     38  */
     39 public final class MidiDevice implements Closeable {
     40     static {
     41         System.loadLibrary("media_jni");
     42     }
     43 
     44     private static final String TAG = "MidiDevice";
     45 
     46     private final MidiDeviceInfo mDeviceInfo;
     47     private final IMidiDeviceServer mDeviceServer;
     48     private final IMidiManager mMidiManager;
     49     private final IBinder mClientToken;
     50     private final IBinder mDeviceToken;
     51     private boolean mIsDeviceClosed;
     52 
     53     // Native API Helpers
     54     /**
     55      * Keep a static list of MidiDevice objects that are mirrorToNative()'d so they
     56      * don't get inadvertantly garbage collected.
     57      */
     58     private static HashSet<MidiDevice> mMirroredDevices = new HashSet<MidiDevice>();
     59 
     60     /**
     61      * If this device is mirrorToNatived(), this is the native device handler.
     62      */
     63     private long mNativeHandle;
     64 
     65     private final CloseGuard mGuard = CloseGuard.get();
     66 
     67     /**
     68      * This class represents a connection between the output port of one device
     69      * and the input port of another. Created by {@link #connectPorts}.
     70      * Close this object to terminate the connection.
     71      */
     72     public class MidiConnection implements Closeable {
     73         private final IMidiDeviceServer mInputPortDeviceServer;
     74         private final IBinder mInputPortToken;
     75         private final IBinder mOutputPortToken;
     76         private final CloseGuard mGuard = CloseGuard.get();
     77         private boolean mIsClosed;
     78 
     79         MidiConnection(IBinder outputPortToken, MidiInputPort inputPort) {
     80             mInputPortDeviceServer = inputPort.getDeviceServer();
     81             mInputPortToken = inputPort.getToken();
     82             mOutputPortToken = outputPortToken;
     83             mGuard.open("close");
     84         }
     85 
     86         @Override
     87         public void close() throws IOException {
     88             synchronized (mGuard) {
     89                 if (mIsClosed) return;
     90                 mGuard.close();
     91                 try {
     92                     // close input port
     93                     mInputPortDeviceServer.closePort(mInputPortToken);
     94                     // close output port
     95                     mDeviceServer.closePort(mOutputPortToken);
     96                 } catch (RemoteException e) {
     97                     Log.e(TAG, "RemoteException in MidiConnection.close");
     98                 }
     99                 mIsClosed = true;
    100             }
    101         }
    102 
    103         @Override
    104         protected void finalize() throws Throwable {
    105             try {
    106                 if (mGuard != null) {
    107                     mGuard.warnIfOpen();
    108                 }
    109 
    110                 close();
    111             } finally {
    112                 super.finalize();
    113             }
    114         }
    115     }
    116 
    117     /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server,
    118             IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) {
    119         mDeviceInfo = deviceInfo;
    120         mDeviceServer = server;
    121         mMidiManager = midiManager;
    122         mClientToken = clientToken;
    123         mDeviceToken = deviceToken;
    124         mGuard.open("close");
    125     }
    126 
    127     /**
    128      * Returns a {@link MidiDeviceInfo} object, which describes this device.
    129      *
    130      * @return the {@link MidiDeviceInfo} object
    131      */
    132     public MidiDeviceInfo getInfo() {
    133         return mDeviceInfo;
    134     }
    135 
    136     /**
    137      * Called to open a {@link MidiInputPort} for the specified port number.
    138      *
    139      * An input port can only be used by one sender at a time.
    140      * Opening an input port will fail if another application has already opened it for use.
    141      * A {@link MidiDeviceStatus} can be used to determine if an input port is already open.
    142      *
    143      * @param portNumber the number of the input port to open
    144      * @return the {@link MidiInputPort} if the open is successful,
    145      *         or null in case of failure.
    146      */
    147     public MidiInputPort openInputPort(int portNumber) {
    148         if (mIsDeviceClosed) {
    149             return null;
    150         }
    151         try {
    152             IBinder token = new Binder();
    153             FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber);
    154             if (fd == null) {
    155                 return null;
    156             }
    157             return new MidiInputPort(mDeviceServer, token, fd, portNumber);
    158         } catch (RemoteException e) {
    159             Log.e(TAG, "RemoteException in openInputPort");
    160             return null;
    161         }
    162     }
    163 
    164     /**
    165      * Called to open a {@link MidiOutputPort} for the specified port number.
    166      *
    167      * An output port may be opened by multiple applications.
    168      *
    169      * @param portNumber the number of the output port to open
    170      * @return the {@link MidiOutputPort} if the open is successful,
    171      *         or null in case of failure.
    172      */
    173     public MidiOutputPort openOutputPort(int portNumber) {
    174         if (mIsDeviceClosed) {
    175             return null;
    176         }
    177         try {
    178             IBinder token = new Binder();
    179             FileDescriptor fd = mDeviceServer.openOutputPort(token, portNumber);
    180             if (fd == null) {
    181                 return null;
    182             }
    183             return new MidiOutputPort(mDeviceServer, token, fd, portNumber);
    184         } catch (RemoteException e) {
    185             Log.e(TAG, "RemoteException in openOutputPort");
    186             return null;
    187         }
    188     }
    189 
    190     /**
    191      * Connects the supplied {@link MidiInputPort} to the output port of this device
    192      * with the specified port number. Once the connection is made, the MidiInput port instance
    193      * can no longer receive data via its {@link MidiReceiver#onSend} method.
    194      * This method returns a {@link MidiDevice.MidiConnection} object, which can be used
    195      * to close the connection.
    196      *
    197      * @param inputPort the inputPort to connect
    198      * @param outputPortNumber the port number of the output port to connect inputPort to.
    199      * @return {@link MidiDevice.MidiConnection} object if the connection is successful,
    200      *         or null in case of failure.
    201      */
    202     public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) {
    203         if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
    204             throw new IllegalArgumentException("outputPortNumber out of range");
    205         }
    206         if (mIsDeviceClosed) {
    207             return null;
    208         }
    209 
    210         FileDescriptor fd = inputPort.claimFileDescriptor();
    211         if (fd == null) {
    212             return null;
    213         }
    214         try {
    215             IBinder token = new Binder();
    216             int calleePid = mDeviceServer.connectPorts(token, fd, outputPortNumber);
    217             // If the service is a different Process then it will duplicate the fd
    218             // and we can safely close this one.
    219             // But if the service is in the same Process then closing the fd will
    220             // kill the connection. So don't do that.
    221             if (calleePid != Process.myPid()) {
    222                 // close our copy of the file descriptor
    223                 IoUtils.closeQuietly(fd);
    224             }
    225 
    226             return new MidiConnection(token, inputPort);
    227         } catch (RemoteException e) {
    228             Log.e(TAG, "RemoteException in connectPorts");
    229             return null;
    230         }
    231     }
    232 
    233     /**
    234      * Makes Midi Device available to the Native API
    235      * @hide
    236      */
    237     public long mirrorToNative() throws IOException {
    238         if (mIsDeviceClosed || mNativeHandle != 0) {
    239             return 0;
    240         }
    241 
    242         mNativeHandle = native_mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId());
    243         if (mNativeHandle == 0) {
    244             throw new IOException("Failed mirroring to native");
    245         }
    246 
    247         synchronized (mMirroredDevices) {
    248             mMirroredDevices.add(this);
    249         }
    250         return mNativeHandle;
    251     }
    252 
    253     /**
    254      * Makes Midi Device no longer available to the Native API
    255      * @hide
    256      */
    257     public void removeFromNative() {
    258         if (mNativeHandle == 0) {
    259             return;
    260         }
    261 
    262         synchronized (mGuard) {
    263             native_removeFromNative(mNativeHandle);
    264             mNativeHandle = 0;
    265         }
    266 
    267         synchronized (mMirroredDevices) {
    268             mMirroredDevices.remove(this);
    269         }
    270     }
    271 
    272     @Override
    273     public void close() throws IOException {
    274         synchronized (mGuard) {
    275             if (!mIsDeviceClosed) {
    276                 removeFromNative();
    277                 mGuard.close();
    278                 mIsDeviceClosed = true;
    279                 try {
    280                     mMidiManager.closeDevice(mClientToken, mDeviceToken);
    281                 } catch (RemoteException e) {
    282                     Log.e(TAG, "RemoteException in closeDevice");
    283                 }
    284             }
    285         }
    286     }
    287 
    288     @Override
    289     protected void finalize() throws Throwable {
    290         try {
    291             if (mGuard != null) {
    292                 mGuard.warnIfOpen();
    293             }
    294 
    295             close();
    296         } finally {
    297             super.finalize();
    298         }
    299     }
    300 
    301     @Override
    302     public String toString() {
    303         return ("MidiDevice: " + mDeviceInfo.toString());
    304     }
    305 
    306     private native long native_mirrorToNative(IBinder deviceServerBinder, int id);
    307     private native void native_removeFromNative(long deviceHandle);
    308 }
    309