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.system.ErrnoException;
     24 import android.system.Os;
     25 import android.system.OsConstants;
     26 import android.util.Log;
     27 
     28 import com.android.internal.midi.MidiDispatcher;
     29 
     30 import dalvik.system.CloseGuard;
     31 
     32 import libcore.io.IoUtils;
     33 
     34 import java.io.Closeable;
     35 import java.io.FileDescriptor;
     36 import java.io.IOException;
     37 import java.util.HashMap;
     38 import java.util.concurrent.CopyOnWriteArrayList;
     39 
     40 /**
     41  * Internal class used for providing an implementation for a MIDI device.
     42  *
     43  * @hide
     44  */
     45 public final class MidiDeviceServer implements Closeable {
     46     private static final String TAG = "MidiDeviceServer";
     47 
     48     private final IMidiManager mMidiManager;
     49 
     50     // MidiDeviceInfo for the device implemented by this server
     51     private MidiDeviceInfo mDeviceInfo;
     52     private final int mInputPortCount;
     53     private final int mOutputPortCount;
     54 
     55     // MidiReceivers for receiving data on our input ports
     56     private final MidiReceiver[] mInputPortReceivers;
     57 
     58     // MidiDispatchers for sending data on our output ports
     59     private MidiDispatcher[] mOutputPortDispatchers;
     60 
     61     // MidiOutputPorts for clients connected to our input ports
     62     private final MidiOutputPort[] mInputPortOutputPorts;
     63 
     64     // List of all MidiInputPorts we created
     65     private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
     66             = new CopyOnWriteArrayList<MidiInputPort>();
     67 
     68 
     69     // for reporting device status
     70     private final boolean[] mInputPortOpen;
     71     private final int[] mOutputPortOpenCount;
     72 
     73     private final CloseGuard mGuard = CloseGuard.get();
     74     private boolean mIsClosed;
     75 
     76     private final Callback mCallback;
     77 
     78     private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
     79     private final HashMap<MidiInputPort, PortClient> mInputPortClients =
     80             new HashMap<MidiInputPort, PortClient>();
     81 
     82     public interface Callback {
     83         /**
     84          * Called to notify when an our device status has changed
     85          * @param server the {@link MidiDeviceServer} that changed
     86          * @param status the {@link MidiDeviceStatus} for the device
     87          */
     88         public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
     89 
     90         /**
     91          * Called to notify when the device is closed
     92          */
     93         public void onClose();
     94     }
     95 
     96     abstract private class PortClient implements IBinder.DeathRecipient {
     97         final IBinder mToken;
     98 
     99         PortClient(IBinder token) {
    100             mToken = token;
    101 
    102             try {
    103                 token.linkToDeath(this, 0);
    104             } catch (RemoteException e) {
    105                 close();
    106             }
    107         }
    108 
    109         abstract void close();
    110 
    111         MidiInputPort getInputPort() {
    112             return null;
    113         }
    114 
    115         @Override
    116         public void binderDied() {
    117             close();
    118         }
    119     }
    120 
    121     private class InputPortClient extends PortClient {
    122         private final MidiOutputPort mOutputPort;
    123 
    124         InputPortClient(IBinder token, MidiOutputPort outputPort) {
    125             super(token);
    126             mOutputPort = outputPort;
    127         }
    128 
    129         @Override
    130         void close() {
    131             mToken.unlinkToDeath(this, 0);
    132             synchronized (mInputPortOutputPorts) {
    133                 int portNumber = mOutputPort.getPortNumber();
    134                 mInputPortOutputPorts[portNumber] = null;
    135                 mInputPortOpen[portNumber] = false;
    136                 updateDeviceStatus();
    137             }
    138             IoUtils.closeQuietly(mOutputPort);
    139         }
    140     }
    141 
    142     private class OutputPortClient extends PortClient {
    143         private final MidiInputPort mInputPort;
    144 
    145         OutputPortClient(IBinder token, MidiInputPort inputPort) {
    146             super(token);
    147             mInputPort = inputPort;
    148         }
    149 
    150         @Override
    151         void close() {
    152             mToken.unlinkToDeath(this, 0);
    153             int portNumber = mInputPort.getPortNumber();
    154             MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
    155             synchronized (dispatcher) {
    156                 dispatcher.getSender().disconnect(mInputPort);
    157                 int openCount = dispatcher.getReceiverCount();
    158                 mOutputPortOpenCount[portNumber] = openCount;
    159                 updateDeviceStatus();
    160            }
    161 
    162             mInputPorts.remove(mInputPort);
    163             IoUtils.closeQuietly(mInputPort);
    164         }
    165 
    166         @Override
    167         MidiInputPort getInputPort() {
    168             return mInputPort;
    169         }
    170     }
    171 
    172     private static FileDescriptor[] createSeqPacketSocketPair() throws IOException {
    173         try {
    174             final FileDescriptor fd0 = new FileDescriptor();
    175             final FileDescriptor fd1 = new FileDescriptor();
    176             Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, fd0, fd1);
    177             return new FileDescriptor[] { fd0, fd1 };
    178         } catch (ErrnoException e) {
    179             throw e.rethrowAsIOException();
    180         }
    181     }
    182 
    183     // Binder interface stub for receiving connection requests from clients
    184     private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
    185 
    186         @Override
    187         public FileDescriptor openInputPort(IBinder token, int portNumber) {
    188             if (mDeviceInfo.isPrivate()) {
    189                 if (Binder.getCallingUid() != Process.myUid()) {
    190                     throw new SecurityException("Can't access private device from different UID");
    191                 }
    192             }
    193 
    194             if (portNumber < 0 || portNumber >= mInputPortCount) {
    195                 Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
    196                 return null;
    197             }
    198 
    199             synchronized (mInputPortOutputPorts) {
    200                 if (mInputPortOutputPorts[portNumber] != null) {
    201                     Log.d(TAG, "port " + portNumber + " already open");
    202                     return null;
    203                 }
    204 
    205                 try {
    206                     FileDescriptor[] pair = createSeqPacketSocketPair();
    207                     MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
    208                     mInputPortOutputPorts[portNumber] = outputPort;
    209                     outputPort.connect(mInputPortReceivers[portNumber]);
    210                     InputPortClient client = new InputPortClient(token, outputPort);
    211                     synchronized (mPortClients) {
    212                         mPortClients.put(token, client);
    213                     }
    214                     mInputPortOpen[portNumber] = true;
    215                     updateDeviceStatus();
    216                     return pair[1];
    217                 } catch (IOException e) {
    218                     Log.e(TAG, "unable to create FileDescriptors in openInputPort");
    219                     return null;
    220                 }
    221             }
    222         }
    223 
    224         @Override
    225         public FileDescriptor openOutputPort(IBinder token, int portNumber) {
    226             if (mDeviceInfo.isPrivate()) {
    227                 if (Binder.getCallingUid() != Process.myUid()) {
    228                     throw new SecurityException("Can't access private device from different UID");
    229                 }
    230             }
    231 
    232             if (portNumber < 0 || portNumber >= mOutputPortCount) {
    233                 Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
    234                 return null;
    235             }
    236 
    237             try {
    238                 FileDescriptor[] pair = createSeqPacketSocketPair();
    239                 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
    240                 // Undo the default blocking-mode of the server-side socket for
    241                 // physical devices to avoid stalling the Java device handler if
    242                 // client app code gets stuck inside 'onSend' handler.
    243                 if (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL) {
    244                     IoUtils.setBlocking(pair[0], false);
    245                 }
    246                 MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
    247                 synchronized (dispatcher) {
    248                     dispatcher.getSender().connect(inputPort);
    249                     int openCount = dispatcher.getReceiverCount();
    250                     mOutputPortOpenCount[portNumber] = openCount;
    251                     updateDeviceStatus();
    252                 }
    253 
    254                 mInputPorts.add(inputPort);
    255                 OutputPortClient client = new OutputPortClient(token, inputPort);
    256                 synchronized (mPortClients) {
    257                     mPortClients.put(token, client);
    258                 }
    259                 synchronized (mInputPortClients) {
    260                     mInputPortClients.put(inputPort, client);
    261                 }
    262                 return pair[1];
    263             } catch (IOException e) {
    264                 Log.e(TAG, "unable to create FileDescriptors in openOutputPort");
    265                 return null;
    266             }
    267         }
    268 
    269         @Override
    270         public void closePort(IBinder token) {
    271             MidiInputPort inputPort = null;
    272             synchronized (mPortClients) {
    273                 PortClient client = mPortClients.remove(token);
    274                 if (client != null) {
    275                     inputPort = client.getInputPort();
    276                     client.close();
    277                 }
    278             }
    279             if (inputPort != null) {
    280                 synchronized (mInputPortClients) {
    281                     mInputPortClients.remove(inputPort);
    282                 }
    283             }
    284         }
    285 
    286         @Override
    287         public void closeDevice() {
    288             if (mCallback != null) {
    289                 mCallback.onClose();
    290             }
    291             IoUtils.closeQuietly(MidiDeviceServer.this);
    292         }
    293 
    294         @Override
    295         public int connectPorts(IBinder token, FileDescriptor fd,
    296                 int outputPortNumber) {
    297             MidiInputPort inputPort = new MidiInputPort(fd, outputPortNumber);
    298             MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber];
    299             synchronized (dispatcher) {
    300                 dispatcher.getSender().connect(inputPort);
    301                 int openCount = dispatcher.getReceiverCount();
    302                 mOutputPortOpenCount[outputPortNumber] = openCount;
    303                 updateDeviceStatus();
    304             }
    305 
    306             mInputPorts.add(inputPort);
    307             OutputPortClient client = new OutputPortClient(token, inputPort);
    308             synchronized (mPortClients) {
    309                 mPortClients.put(token, client);
    310             }
    311             synchronized (mInputPortClients) {
    312                 mInputPortClients.put(inputPort, client);
    313             }
    314             return Process.myPid(); // for caller to detect same process ID
    315         }
    316 
    317         @Override
    318         public MidiDeviceInfo getDeviceInfo() {
    319             return mDeviceInfo;
    320         }
    321 
    322         @Override
    323         public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
    324             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
    325                 throw new SecurityException("setDeviceInfo should only be called by MidiService");
    326             }
    327             if (mDeviceInfo != null) {
    328                 throw new IllegalStateException("setDeviceInfo should only be called once");
    329             }
    330             mDeviceInfo = deviceInfo;
    331         }
    332     };
    333 
    334     // Constructor for MidiManager.createDeviceServer()
    335     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
    336             int numOutputPorts, Callback callback) {
    337         mMidiManager = midiManager;
    338         mInputPortReceivers = inputPortReceivers;
    339         mInputPortCount = inputPortReceivers.length;
    340         mOutputPortCount = numOutputPorts;
    341         mCallback = callback;
    342 
    343         mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
    344 
    345         mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
    346         for (int i = 0; i < numOutputPorts; i++) {
    347             mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
    348         }
    349 
    350         mInputPortOpen = new boolean[mInputPortCount];
    351         mOutputPortOpenCount = new int[numOutputPorts];
    352 
    353         mGuard.open("close");
    354     }
    355 
    356     private final MidiDispatcher.MidiReceiverFailureHandler mInputPortFailureHandler =
    357             new MidiDispatcher.MidiReceiverFailureHandler() {
    358                 public void onReceiverFailure(MidiReceiver receiver, IOException failure) {
    359                     Log.e(TAG, "MidiInputPort failed to send data", failure);
    360                     PortClient client = null;
    361                     synchronized (mInputPortClients) {
    362                         client = mInputPortClients.remove(receiver);
    363                     }
    364                     if (client != null) {
    365                         client.close();
    366                     }
    367                 }
    368             };
    369 
    370     // Constructor for MidiDeviceService.onCreate()
    371     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
    372            MidiDeviceInfo deviceInfo, Callback callback) {
    373         this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
    374         mDeviceInfo = deviceInfo;
    375     }
    376 
    377     /* package */ IMidiDeviceServer getBinderInterface() {
    378         return mServer;
    379     }
    380 
    381     public IBinder asBinder() {
    382         return mServer.asBinder();
    383     }
    384 
    385     private void updateDeviceStatus() {
    386         // clear calling identity, since we may be in a Binder call from one of our clients
    387         long identityToken = Binder.clearCallingIdentity();
    388 
    389         MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
    390                 mOutputPortOpenCount);
    391         if (mCallback != null) {
    392             mCallback.onDeviceStatusChanged(this, status);
    393         }
    394         try {
    395             mMidiManager.setDeviceStatus(mServer, status);
    396         } catch (RemoteException e) {
    397             Log.e(TAG, "RemoteException in updateDeviceStatus");
    398         } finally {
    399             Binder.restoreCallingIdentity(identityToken);
    400         }
    401     }
    402 
    403     @Override
    404     public void close() throws IOException {
    405         synchronized (mGuard) {
    406             if (mIsClosed) return;
    407             mGuard.close();
    408 
    409             for (int i = 0; i < mInputPortCount; i++) {
    410                 MidiOutputPort outputPort = mInputPortOutputPorts[i];
    411                 if (outputPort != null) {
    412                     IoUtils.closeQuietly(outputPort);
    413                     mInputPortOutputPorts[i] = null;
    414                 }
    415             }
    416             for (MidiInputPort inputPort : mInputPorts) {
    417                 IoUtils.closeQuietly(inputPort);
    418             }
    419             mInputPorts.clear();
    420             try {
    421                 mMidiManager.unregisterDeviceServer(mServer);
    422             } catch (RemoteException e) {
    423                 Log.e(TAG, "RemoteException in unregisterDeviceServer");
    424             }
    425             mIsClosed = true;
    426         }
    427     }
    428 
    429     @Override
    430     protected void finalize() throws Throwable {
    431         try {
    432             if (mGuard != null) {
    433                 mGuard.warnIfOpen();
    434             }
    435 
    436             close();
    437         } finally {
    438             super.finalize();
    439         }
    440     }
    441 
    442     /**
    443      * Returns an array of {@link MidiReceiver} for the device's output ports.
    444      * Clients can use these receivers to send data out the device's output ports.
    445      * @return array of MidiReceivers
    446      */
    447     public MidiReceiver[] getOutputPortReceivers() {
    448         MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
    449         System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
    450         return receivers;
    451     }
    452 }
    453