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