MidiDeviceServer.java revision 5ff9e2a1719f78cddc7a23d6572ab15ab595dafd
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.Binder; 20import android.os.IBinder; 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 65 // for reporting device status 66 private final IBinder mDeviceStatusToken = new Binder(); 67 private final boolean[] mInputPortBusy; 68 private final int[] mOutputPortOpenCount; 69 70 private final CloseGuard mGuard = CloseGuard.get(); 71 private boolean mIsClosed; 72 73 private final Callback mCallback; 74 75 public interface Callback { 76 /** 77 * Called to notify when an our device status has changed 78 * @param server the {@link MidiDeviceServer} that changed 79 * @param status the {@link MidiDeviceStatus} for the device 80 */ 81 public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status); 82 } 83 84 abstract private class PortClient implements IBinder.DeathRecipient { 85 final IBinder mToken; 86 87 PortClient(IBinder token) { 88 mToken = token; 89 90 try { 91 token.linkToDeath(this, 0); 92 } catch (RemoteException e) { 93 close(); 94 } 95 } 96 97 abstract void close(); 98 99 @Override 100 public void binderDied() { 101 close(); 102 } 103 } 104 105 private class InputPortClient extends PortClient { 106 private final MidiOutputPort mOutputPort; 107 108 InputPortClient(IBinder token, MidiOutputPort outputPort) { 109 super(token); 110 mOutputPort = outputPort; 111 } 112 113 @Override 114 void close() { 115 mToken.unlinkToDeath(this, 0); 116 synchronized (mInputPortOutputPorts) { 117 int portNumber = mOutputPort.getPortNumber(); 118 mInputPortOutputPorts[portNumber] = null; 119 mInputPortBusy[portNumber] = false; 120 updateDeviceStatus(); 121 } 122 IoUtils.closeQuietly(mOutputPort); 123 } 124 } 125 126 private class OutputPortClient extends PortClient { 127 private final MidiInputPort mInputPort; 128 129 OutputPortClient(IBinder token, MidiInputPort inputPort) { 130 super(token); 131 mInputPort = inputPort; 132 } 133 134 @Override 135 void close() { 136 mToken.unlinkToDeath(this, 0); 137 int portNumber = mInputPort.getPortNumber(); 138 MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber]; 139 synchronized (dispatcher) { 140 dispatcher.getSender().disconnect(mInputPort); 141 int openCount = dispatcher.getReceiverCount(); 142 mOutputPortOpenCount[portNumber] = openCount; 143 updateDeviceStatus(); 144 } 145 146 mInputPorts.remove(mInputPort); 147 IoUtils.closeQuietly(mInputPort); 148 } 149 } 150 151 private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>(); 152 153 // Binder interface stub for receiving connection requests from clients 154 private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { 155 156 @Override 157 public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) { 158 if (mDeviceInfo.isPrivate()) { 159 if (Binder.getCallingUid() != Process.myUid()) { 160 throw new SecurityException("Can't access private device from different UID"); 161 } 162 } 163 164 if (portNumber < 0 || portNumber >= mInputPortCount) { 165 Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber); 166 return null; 167 } 168 169 synchronized (mInputPortOutputPorts) { 170 if (mInputPortOutputPorts[portNumber] != null) { 171 Log.d(TAG, "port " + portNumber + " already open"); 172 return null; 173 } 174 175 try { 176 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( 177 OsConstants.SOCK_SEQPACKET); 178 MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber); 179 mInputPortOutputPorts[portNumber] = outputPort; 180 outputPort.connect(mInputPortReceivers[portNumber]); 181 InputPortClient client = new InputPortClient(token, outputPort); 182 synchronized (mPortClients) { 183 mPortClients.put(token, client); 184 } 185 mInputPortBusy[portNumber] = true; 186 updateDeviceStatus(); 187 return pair[1]; 188 } catch (IOException e) { 189 Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); 190 return null; 191 } 192 } 193 } 194 195 @Override 196 public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) { 197 if (mDeviceInfo.isPrivate()) { 198 if (Binder.getCallingUid() != Process.myUid()) { 199 throw new SecurityException("Can't access private device from different UID"); 200 } 201 } 202 203 if (portNumber < 0 || portNumber >= mOutputPortCount) { 204 Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber); 205 return null; 206 } 207 208 try { 209 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( 210 OsConstants.SOCK_SEQPACKET); 211 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber); 212 MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber]; 213 synchronized (dispatcher) { 214 dispatcher.getSender().connect(inputPort); 215 int openCount = dispatcher.getReceiverCount(); 216 mOutputPortOpenCount[portNumber] = openCount; 217 updateDeviceStatus(); 218 } 219 220 mInputPorts.add(inputPort); 221 OutputPortClient client = new OutputPortClient(token, inputPort); 222 synchronized (mPortClients) { 223 mPortClients.put(token, client); 224 } 225 return pair[1]; 226 } catch (IOException e) { 227 Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); 228 return null; 229 } 230 } 231 232 @Override 233 public void closePort(IBinder token) { 234 synchronized (mPortClients) { 235 PortClient client = mPortClients.remove(token); 236 if (client != null) { 237 client.close(); 238 } 239 } 240 } 241 242 @Override 243 public void connectPorts(IBinder token, ParcelFileDescriptor pfd, 244 int outputPortNumber) { 245 MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber); 246 mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort); 247 mInputPorts.add(inputPort); 248 OutputPortClient client = new OutputPortClient(token, inputPort); 249 synchronized (mPortClients) { 250 mPortClients.put(token, client); 251 } 252 } 253 }; 254 255 /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, 256 int numOutputPorts, Callback callback) { 257 mMidiManager = midiManager; 258 mInputPortReceivers = inputPortReceivers; 259 mInputPortCount = inputPortReceivers.length; 260 mOutputPortCount = numOutputPorts; 261 mCallback = callback; 262 263 mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; 264 265 mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; 266 for (int i = 0; i < numOutputPorts; i++) { 267 mOutputPortDispatchers[i] = new MidiDispatcher(); 268 } 269 270 mInputPortBusy = new boolean[mInputPortCount]; 271 mOutputPortOpenCount = new int[numOutputPorts]; 272 273 mGuard.open("close"); 274 } 275 276 /* package */ IMidiDeviceServer getBinderInterface() { 277 return mServer; 278 } 279 280 /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { 281 if (mDeviceInfo != null) { 282 throw new IllegalStateException("setDeviceInfo should only be called once"); 283 } 284 mDeviceInfo = deviceInfo; 285 } 286 287 private void updateDeviceStatus() { 288 // clear calling identity, since we may be in a Binder call from one of our clients 289 long identityToken = Binder.clearCallingIdentity(); 290 291 MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy, 292 mOutputPortOpenCount); 293 if (mCallback != null) { 294 mCallback.onDeviceStatusChanged(this, status); 295 } 296 try { 297 mMidiManager.setDeviceStatus(mDeviceStatusToken, status); 298 } catch (RemoteException e) { 299 Log.e(TAG, "RemoteException in updateDeviceStatus"); 300 } finally { 301 Binder.restoreCallingIdentity(identityToken); 302 } 303 } 304 305 @Override 306 public void close() throws IOException { 307 synchronized (mGuard) { 308 if (mIsClosed) return; 309 mGuard.close(); 310 311 for (int i = 0; i < mInputPortCount; i++) { 312 MidiOutputPort outputPort = mInputPortOutputPorts[i]; 313 if (outputPort != null) { 314 IoUtils.closeQuietly(outputPort); 315 mInputPortOutputPorts[i] = null; 316 } 317 } 318 for (MidiInputPort inputPort : mInputPorts) { 319 IoUtils.closeQuietly(inputPort); 320 } 321 mInputPorts.clear(); 322 try { 323 mMidiManager.unregisterDeviceServer(mServer); 324 } catch (RemoteException e) { 325 Log.e(TAG, "RemoteException in unregisterDeviceServer"); 326 } 327 mIsClosed = true; 328 } 329 } 330 331 @Override 332 protected void finalize() throws Throwable { 333 try { 334 mGuard.warnIfOpen(); 335 close(); 336 } finally { 337 super.finalize(); 338 } 339 } 340 341 /** 342 * Returns an array of {@link MidiReceiver} for the device's output ports. 343 * Clients can use these receivers to send data out the device's output ports. 344 * @return array of MidiReceivers 345 */ 346 public MidiReceiver[] getOutputPortReceivers() { 347 MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount]; 348 System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount); 349 return receivers; 350 } 351} 352