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