MidiDeviceServer.java revision 46326e59a0a19367d4158c027d56d4b8440e8d3d
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 17package android.media.midi; 18 19import android.os.IBinder; 20import android.os.Binder; 21import android.os.ParcelFileDescriptor; 22import android.os.Process; 23import android.os.RemoteException; 24import android.system.OsConstants; 25import android.util.Log; 26 27import dalvik.system.CloseGuard; 28 29import libcore.io.IoUtils; 30 31import java.io.Closeable; 32import java.io.IOException; 33import java.util.HashMap; 34import java.util.concurrent.CopyOnWriteArrayList; 35 36/** 37 * Internal class used for providing an implementation for a MIDI device. 38 * 39 * @hide 40 */ 41public final class MidiDeviceServer implements Closeable { 42 private static final String TAG = "MidiDeviceServer"; 43 44 private final IMidiManager mMidiManager; 45 46 // MidiDeviceInfo for the device implemented by this server 47 private MidiDeviceInfo mDeviceInfo; 48 private final int mInputPortCount; 49 private final int mOutputPortCount; 50 51 // MidiReceivers for receiving data on our input ports 52 private final MidiReceiver[] mInputPortReceivers; 53 54 // MidiDispatchers for sending data on our output ports 55 private MidiDispatcher[] mOutputPortDispatchers; 56 57 // MidiOutputPorts for clients connected to our input ports 58 private final MidiOutputPort[] mInputPortOutputPorts; 59 60 // List of all MidiInputPorts we created 61 private final CopyOnWriteArrayList<MidiInputPort> mInputPorts 62 = new CopyOnWriteArrayList<MidiInputPort>(); 63 64 private final CloseGuard mGuard = CloseGuard.get(); 65 66 abstract private class PortClient implements IBinder.DeathRecipient { 67 final IBinder mToken; 68 69 PortClient(IBinder token) { 70 mToken = token; 71 72 try { 73 token.linkToDeath(this, 0); 74 } catch (RemoteException e) { 75 close(); 76 } 77 } 78 79 abstract void close(); 80 81 @Override 82 public void binderDied() { 83 close(); 84 } 85 } 86 87 private class InputPortClient extends PortClient { 88 private final MidiOutputPort mOutputPort; 89 90 InputPortClient(IBinder token, MidiOutputPort outputPort) { 91 super(token); 92 mOutputPort = outputPort; 93 } 94 95 @Override 96 void close() { 97 mToken.unlinkToDeath(this, 0); 98 synchronized (mInputPortOutputPorts) { 99 mInputPortOutputPorts[mOutputPort.getPortNumber()] = null; 100 } 101 IoUtils.closeQuietly(mOutputPort); 102 } 103 } 104 105 private class OutputPortClient extends PortClient { 106 private final MidiInputPort mInputPort; 107 108 OutputPortClient(IBinder token, MidiInputPort inputPort) { 109 super(token); 110 mInputPort = inputPort; 111 } 112 113 @Override 114 void close() { 115 mToken.unlinkToDeath(this, 0); 116 mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort); 117 mInputPorts.remove(mInputPort); 118 IoUtils.closeQuietly(mInputPort); 119 } 120 } 121 122 private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>(); 123 124 // Binder interface stub for receiving connection requests from clients 125 private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { 126 127 @Override 128 public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) { 129 if (mDeviceInfo.isPrivate()) { 130 if (Binder.getCallingUid() != Process.myUid()) { 131 throw new SecurityException("Can't access private device from different UID"); 132 } 133 } 134 135 if (portNumber < 0 || portNumber >= mInputPortCount) { 136 Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber); 137 return null; 138 } 139 140 synchronized (mInputPortOutputPorts) { 141 if (mInputPortOutputPorts[portNumber] != null) { 142 Log.d(TAG, "port " + portNumber + " already open"); 143 return null; 144 } 145 146 try { 147 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( 148 OsConstants.SOCK_SEQPACKET); 149 MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber); 150 mInputPortOutputPorts[portNumber] = outputPort; 151 outputPort.connect(mInputPortReceivers[portNumber]); 152 InputPortClient client = new InputPortClient(token, outputPort); 153 synchronized (mPortClients) { 154 mPortClients.put(token, client); 155 } 156 return pair[1]; 157 } catch (IOException e) { 158 Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); 159 return null; 160 } 161 } 162 } 163 164 @Override 165 public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) { 166 if (mDeviceInfo.isPrivate()) { 167 if (Binder.getCallingUid() != Process.myUid()) { 168 throw new SecurityException("Can't access private device from different UID"); 169 } 170 } 171 172 if (portNumber < 0 || portNumber >= mOutputPortCount) { 173 Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber); 174 return null; 175 } 176 177 try { 178 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( 179 OsConstants.SOCK_SEQPACKET); 180 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber); 181 mOutputPortDispatchers[portNumber].getSender().connect(inputPort); 182 mInputPorts.add(inputPort); 183 OutputPortClient client = new OutputPortClient(token, inputPort); 184 synchronized (mPortClients) { 185 mPortClients.put(token, client); 186 } 187 return pair[1]; 188 } catch (IOException e) { 189 Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); 190 return null; 191 } 192 } 193 194 @Override 195 public void closePort(IBinder token) { 196 synchronized (mPortClients) { 197 PortClient client = mPortClients.remove(token); 198 if (client != null) { 199 client.close(); 200 } 201 } 202 } 203 204 @Override 205 public void connectPorts(IBinder token, ParcelFileDescriptor pfd, 206 int outputPortNumber) { 207 MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber); 208 mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort); 209 mInputPorts.add(inputPort); 210 OutputPortClient client = new OutputPortClient(token, inputPort); 211 synchronized (mPortClients) { 212 mPortClients.put(token, client); 213 } 214 } 215 }; 216 217 /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, 218 int numOutputPorts) { 219 mMidiManager = midiManager; 220 mInputPortReceivers = inputPortReceivers; 221 mInputPortCount = inputPortReceivers.length; 222 mOutputPortCount = numOutputPorts; 223 224 mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; 225 226 mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; 227 for (int i = 0; i < numOutputPorts; i++) { 228 mOutputPortDispatchers[i] = new MidiDispatcher(); 229 } 230 231 mGuard.open("close"); 232 } 233 234 /* package */ IMidiDeviceServer getBinderInterface() { 235 return mServer; 236 } 237 238 /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { 239 if (mDeviceInfo != null) { 240 throw new IllegalStateException("setDeviceInfo should only be called once"); 241 } 242 mDeviceInfo = deviceInfo; 243 } 244 245 @Override 246 public void close() throws IOException { 247 synchronized (mGuard) { 248 mGuard.close(); 249 250 for (int i = 0; i < mInputPortCount; i++) { 251 MidiOutputPort outputPort = mInputPortOutputPorts[i]; 252 if (outputPort != null) { 253 IoUtils.closeQuietly(outputPort); 254 mInputPortOutputPorts[i] = null; 255 } 256 } 257 for (MidiInputPort inputPort : mInputPorts) { 258 IoUtils.closeQuietly(inputPort); 259 } 260 mInputPorts.clear(); 261 try { 262 mMidiManager.unregisterDeviceServer(mServer); 263 } catch (RemoteException e) { 264 Log.e(TAG, "RemoteException in unregisterDeviceServer"); 265 } 266 } 267 } 268 269 @Override 270 protected void finalize() throws Throwable { 271 try { 272 mGuard.warnIfOpen(); 273 close(); 274 } finally { 275 super.finalize(); 276 } 277 } 278 279 /** 280 * Returns an array of {@link MidiReceiver} for the device's output ports. 281 * Clients can use these receivers to send data out the device's output ports. 282 * @return array of MidiReceivers 283 */ 284 public MidiReceiver[] getOutputPortReceivers() { 285 MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount]; 286 System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount); 287 return receivers; 288 } 289} 290