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