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 void 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         }
    274 
    275         @Override
    276         public MidiDeviceInfo getDeviceInfo() {
    277             return mDeviceInfo;
    278         }
    279 
    280         @Override
    281         public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
    282             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
    283                 throw new SecurityException("setDeviceInfo should only be called by MidiService");
    284             }
    285             if (mDeviceInfo != null) {
    286                 throw new IllegalStateException("setDeviceInfo should only be called once");
    287             }
    288             mDeviceInfo = deviceInfo;
    289         }
    290     };
    291 
    292     // Constructor for MidiManager.createDeviceServer()
    293     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
    294             int numOutputPorts, Callback callback) {
    295         mMidiManager = midiManager;
    296         mInputPortReceivers = inputPortReceivers;
    297         mInputPortCount = inputPortReceivers.length;
    298         mOutputPortCount = numOutputPorts;
    299         mCallback = callback;
    300 
    301         mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
    302 
    303         mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
    304         for (int i = 0; i < numOutputPorts; i++) {
    305             mOutputPortDispatchers[i] = new MidiDispatcher();
    306         }
    307 
    308         mInputPortOpen = new boolean[mInputPortCount];
    309         mOutputPortOpenCount = new int[numOutputPorts];
    310 
    311         mGuard.open("close");
    312     }
    313 
    314     // Constructor for MidiDeviceService.onCreate()
    315     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
    316            MidiDeviceInfo deviceInfo, Callback callback) {
    317         this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
    318         mDeviceInfo = deviceInfo;
    319     }
    320 
    321     /* package */ IMidiDeviceServer getBinderInterface() {
    322         return mServer;
    323     }
    324 
    325     public IBinder asBinder() {
    326         return mServer.asBinder();
    327     }
    328 
    329     private void updateDeviceStatus() {
    330         // clear calling identity, since we may be in a Binder call from one of our clients
    331         long identityToken = Binder.clearCallingIdentity();
    332 
    333         MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
    334                 mOutputPortOpenCount);
    335         if (mCallback != null) {
    336             mCallback.onDeviceStatusChanged(this, status);
    337         }
    338         try {
    339             mMidiManager.setDeviceStatus(mServer, status);
    340         } catch (RemoteException e) {
    341             Log.e(TAG, "RemoteException in updateDeviceStatus");
    342         } finally {
    343             Binder.restoreCallingIdentity(identityToken);
    344         }
    345     }
    346 
    347     @Override
    348     public void close() throws IOException {
    349         synchronized (mGuard) {
    350             if (mIsClosed) return;
    351             mGuard.close();
    352 
    353             for (int i = 0; i < mInputPortCount; i++) {
    354                 MidiOutputPort outputPort = mInputPortOutputPorts[i];
    355                 if (outputPort != null) {
    356                     IoUtils.closeQuietly(outputPort);
    357                     mInputPortOutputPorts[i] = null;
    358                 }
    359             }
    360             for (MidiInputPort inputPort : mInputPorts) {
    361                 IoUtils.closeQuietly(inputPort);
    362             }
    363             mInputPorts.clear();
    364             try {
    365                 mMidiManager.unregisterDeviceServer(mServer);
    366             } catch (RemoteException e) {
    367                 Log.e(TAG, "RemoteException in unregisterDeviceServer");
    368             }
    369             mIsClosed = true;
    370         }
    371     }
    372 
    373     @Override
    374     protected void finalize() throws Throwable {
    375         try {
    376             mGuard.warnIfOpen();
    377             close();
    378         } finally {
    379             super.finalize();
    380         }
    381     }
    382 
    383     /**
    384      * Returns an array of {@link MidiReceiver} for the device's output ports.
    385      * Clients can use these receivers to send data out the device's output ports.
    386      * @return array of MidiReceivers
    387      */
    388     public MidiReceiver[] getOutputPortReceivers() {
    389         MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
    390         System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
    391         return receivers;
    392     }
    393 }
    394