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