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