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