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 void 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 } 274 275 @Override 276 public MidiDeviceInfo getDeviceInfo() { 277 return mDeviceInfo; 278 } 279 280 @Override 281 public void setDeviceInfo(MidiDeviceInfo deviceInfo) { 282 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 283 throw new SecurityException("setDeviceInfo should only be called by MidiService"); 284 } 285 if (mDeviceInfo != null) { 286 throw new IllegalStateException("setDeviceInfo should only be called once"); 287 } 288 mDeviceInfo = deviceInfo; 289 } 290 }; 291 292 // Constructor for MidiManager.createDeviceServer() 293 /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, 294 int numOutputPorts, Callback callback) { 295 mMidiManager = midiManager; 296 mInputPortReceivers = inputPortReceivers; 297 mInputPortCount = inputPortReceivers.length; 298 mOutputPortCount = numOutputPorts; 299 mCallback = callback; 300 301 mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; 302 303 mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; 304 for (int i = 0; i < numOutputPorts; i++) { 305 mOutputPortDispatchers[i] = new MidiDispatcher(); 306 } 307 308 mInputPortOpen = new boolean[mInputPortCount]; 309 mOutputPortOpenCount = new int[numOutputPorts]; 310 311 mGuard.open("close"); 312 } 313 314 // Constructor for MidiDeviceService.onCreate() 315 /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, 316 MidiDeviceInfo deviceInfo, Callback callback) { 317 this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback); 318 mDeviceInfo = deviceInfo; 319 } 320 321 /* package */ IMidiDeviceServer getBinderInterface() { 322 return mServer; 323 } 324 325 public IBinder asBinder() { 326 return mServer.asBinder(); 327 } 328 329 private void updateDeviceStatus() { 330 // clear calling identity, since we may be in a Binder call from one of our clients 331 long identityToken = Binder.clearCallingIdentity(); 332 333 MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen, 334 mOutputPortOpenCount); 335 if (mCallback != null) { 336 mCallback.onDeviceStatusChanged(this, status); 337 } 338 try { 339 mMidiManager.setDeviceStatus(mServer, status); 340 } catch (RemoteException e) { 341 Log.e(TAG, "RemoteException in updateDeviceStatus"); 342 } finally { 343 Binder.restoreCallingIdentity(identityToken); 344 } 345 } 346 347 @Override 348 public void close() throws IOException { 349 synchronized (mGuard) { 350 if (mIsClosed) return; 351 mGuard.close(); 352 353 for (int i = 0; i < mInputPortCount; i++) { 354 MidiOutputPort outputPort = mInputPortOutputPorts[i]; 355 if (outputPort != null) { 356 IoUtils.closeQuietly(outputPort); 357 mInputPortOutputPorts[i] = null; 358 } 359 } 360 for (MidiInputPort inputPort : mInputPorts) { 361 IoUtils.closeQuietly(inputPort); 362 } 363 mInputPorts.clear(); 364 try { 365 mMidiManager.unregisterDeviceServer(mServer); 366 } catch (RemoteException e) { 367 Log.e(TAG, "RemoteException in unregisterDeviceServer"); 368 } 369 mIsClosed = true; 370 } 371 } 372 373 @Override 374 protected void finalize() throws Throwable { 375 try { 376 mGuard.warnIfOpen(); 377 close(); 378 } finally { 379 super.finalize(); 380 } 381 } 382 383 /** 384 * Returns an array of {@link MidiReceiver} for the device's output ports. 385 * Clients can use these receivers to send data out the device's output ports. 386 * @return array of MidiReceivers 387 */ 388 public MidiReceiver[] getOutputPortReceivers() { 389 MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount]; 390 System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount); 391 return receivers; 392 } 393 } 394