MidiDeviceServer.java revision d1b16fe2fb7527eee214898263ec4d6dabbfb0b4
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 257 /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, 258 int numOutputPorts, Callback callback) { 259 mMidiManager = midiManager; 260 mInputPortReceivers = inputPortReceivers; 261 mInputPortCount = inputPortReceivers.length; 262 mOutputPortCount = numOutputPorts; 263 mCallback = callback; 264 265 mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; 266 267 mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; 268 for (int i = 0; i < numOutputPorts; i++) { 269 mOutputPortDispatchers[i] = new MidiDispatcher(); 270 } 271 272 mInputPortOpen = new boolean[mInputPortCount]; 273 mOutputPortOpenCount = new int[numOutputPorts]; 274 275 mGuard.open("close"); 276 } 277 278 /* package */ IMidiDeviceServer getBinderInterface() { 279 return mServer; 280 } 281 282 /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { 283 if (mDeviceInfo != null) { 284 throw new IllegalStateException("setDeviceInfo should only be called once"); 285 } 286 mDeviceInfo = deviceInfo; 287 } 288 289 private void updateDeviceStatus() { 290 // clear calling identity, since we may be in a Binder call from one of our clients 291 long identityToken = Binder.clearCallingIdentity(); 292 293 MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen, 294 mOutputPortOpenCount); 295 if (mCallback != null) { 296 mCallback.onDeviceStatusChanged(this, status); 297 } 298 try { 299 mMidiManager.setDeviceStatus(mDeviceStatusToken, status); 300 } catch (RemoteException e) { 301 Log.e(TAG, "RemoteException in updateDeviceStatus"); 302 } finally { 303 Binder.restoreCallingIdentity(identityToken); 304 } 305 } 306 307 @Override 308 public void close() throws IOException { 309 synchronized (mGuard) { 310 if (mIsClosed) return; 311 mGuard.close(); 312 313 for (int i = 0; i < mInputPortCount; i++) { 314 MidiOutputPort outputPort = mInputPortOutputPorts[i]; 315 if (outputPort != null) { 316 IoUtils.closeQuietly(outputPort); 317 mInputPortOutputPorts[i] = null; 318 } 319 } 320 for (MidiInputPort inputPort : mInputPorts) { 321 IoUtils.closeQuietly(inputPort); 322 } 323 mInputPorts.clear(); 324 try { 325 mMidiManager.unregisterDeviceServer(mServer); 326 } catch (RemoteException e) { 327 Log.e(TAG, "RemoteException in unregisterDeviceServer"); 328 } 329 mIsClosed = true; 330 } 331 } 332 333 @Override 334 protected void finalize() throws Throwable { 335 try { 336 mGuard.warnIfOpen(); 337 close(); 338 } finally { 339 super.finalize(); 340 } 341 } 342 343 /** 344 * Returns an array of {@link MidiReceiver} for the device's output ports. 345 * Clients can use these receivers to send data out the device's output ports. 346 * @return array of MidiReceivers 347 */ 348 public MidiReceiver[] getOutputPortReceivers() { 349 MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount]; 350 System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount); 351 return receivers; 352 } 353} 354