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.Process; 22 import android.os.RemoteException; 23 import android.util.Log; 24 25 import dalvik.system.CloseGuard; 26 27 import libcore.io.IoUtils; 28 29 import java.io.Closeable; 30 import java.io.FileDescriptor; 31 import java.io.IOException; 32 33 import java.util.HashSet; 34 35 /** 36 * This class is used for sending and receiving data to and from a MIDI device 37 * Instances of this class are created by {@link MidiManager#openDevice}. 38 */ 39 public final class MidiDevice implements Closeable { 40 static { 41 System.loadLibrary("media_jni"); 42 } 43 44 private static final String TAG = "MidiDevice"; 45 46 private final MidiDeviceInfo mDeviceInfo; 47 private final IMidiDeviceServer mDeviceServer; 48 private final IMidiManager mMidiManager; 49 private final IBinder mClientToken; 50 private final IBinder mDeviceToken; 51 private boolean mIsDeviceClosed; 52 53 // Native API Helpers 54 /** 55 * Keep a static list of MidiDevice objects that are mirrorToNative()'d so they 56 * don't get inadvertantly garbage collected. 57 */ 58 private static HashSet<MidiDevice> mMirroredDevices = new HashSet<MidiDevice>(); 59 60 /** 61 * If this device is mirrorToNatived(), this is the native device handler. 62 */ 63 private long mNativeHandle; 64 65 private final CloseGuard mGuard = CloseGuard.get(); 66 67 /** 68 * This class represents a connection between the output port of one device 69 * and the input port of another. Created by {@link #connectPorts}. 70 * Close this object to terminate the connection. 71 */ 72 public class MidiConnection implements Closeable { 73 private final IMidiDeviceServer mInputPortDeviceServer; 74 private final IBinder mInputPortToken; 75 private final IBinder mOutputPortToken; 76 private final CloseGuard mGuard = CloseGuard.get(); 77 private boolean mIsClosed; 78 79 MidiConnection(IBinder outputPortToken, MidiInputPort inputPort) { 80 mInputPortDeviceServer = inputPort.getDeviceServer(); 81 mInputPortToken = inputPort.getToken(); 82 mOutputPortToken = outputPortToken; 83 mGuard.open("close"); 84 } 85 86 @Override 87 public void close() throws IOException { 88 synchronized (mGuard) { 89 if (mIsClosed) return; 90 mGuard.close(); 91 try { 92 // close input port 93 mInputPortDeviceServer.closePort(mInputPortToken); 94 // close output port 95 mDeviceServer.closePort(mOutputPortToken); 96 } catch (RemoteException e) { 97 Log.e(TAG, "RemoteException in MidiConnection.close"); 98 } 99 mIsClosed = true; 100 } 101 } 102 103 @Override 104 protected void finalize() throws Throwable { 105 try { 106 if (mGuard != null) { 107 mGuard.warnIfOpen(); 108 } 109 110 close(); 111 } finally { 112 super.finalize(); 113 } 114 } 115 } 116 117 /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, 118 IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) { 119 mDeviceInfo = deviceInfo; 120 mDeviceServer = server; 121 mMidiManager = midiManager; 122 mClientToken = clientToken; 123 mDeviceToken = deviceToken; 124 mGuard.open("close"); 125 } 126 127 /** 128 * Returns a {@link MidiDeviceInfo} object, which describes this device. 129 * 130 * @return the {@link MidiDeviceInfo} object 131 */ 132 public MidiDeviceInfo getInfo() { 133 return mDeviceInfo; 134 } 135 136 /** 137 * Called to open a {@link MidiInputPort} for the specified port number. 138 * 139 * An input port can only be used by one sender at a time. 140 * Opening an input port will fail if another application has already opened it for use. 141 * A {@link MidiDeviceStatus} can be used to determine if an input port is already open. 142 * 143 * @param portNumber the number of the input port to open 144 * @return the {@link MidiInputPort} if the open is successful, 145 * or null in case of failure. 146 */ 147 public MidiInputPort openInputPort(int portNumber) { 148 if (mIsDeviceClosed) { 149 return null; 150 } 151 try { 152 IBinder token = new Binder(); 153 FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber); 154 if (fd == null) { 155 return null; 156 } 157 return new MidiInputPort(mDeviceServer, token, fd, portNumber); 158 } catch (RemoteException e) { 159 Log.e(TAG, "RemoteException in openInputPort"); 160 return null; 161 } 162 } 163 164 /** 165 * Called to open a {@link MidiOutputPort} for the specified port number. 166 * 167 * An output port may be opened by multiple applications. 168 * 169 * @param portNumber the number of the output port to open 170 * @return the {@link MidiOutputPort} if the open is successful, 171 * or null in case of failure. 172 */ 173 public MidiOutputPort openOutputPort(int portNumber) { 174 if (mIsDeviceClosed) { 175 return null; 176 } 177 try { 178 IBinder token = new Binder(); 179 FileDescriptor fd = mDeviceServer.openOutputPort(token, portNumber); 180 if (fd == null) { 181 return null; 182 } 183 return new MidiOutputPort(mDeviceServer, token, fd, portNumber); 184 } catch (RemoteException e) { 185 Log.e(TAG, "RemoteException in openOutputPort"); 186 return null; 187 } 188 } 189 190 /** 191 * Connects the supplied {@link MidiInputPort} to the output port of this device 192 * with the specified port number. Once the connection is made, the MidiInput port instance 193 * can no longer receive data via its {@link MidiReceiver#onSend} method. 194 * This method returns a {@link MidiDevice.MidiConnection} object, which can be used 195 * to close the connection. 196 * 197 * @param inputPort the inputPort to connect 198 * @param outputPortNumber the port number of the output port to connect inputPort to. 199 * @return {@link MidiDevice.MidiConnection} object if the connection is successful, 200 * or null in case of failure. 201 */ 202 public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) { 203 if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) { 204 throw new IllegalArgumentException("outputPortNumber out of range"); 205 } 206 if (mIsDeviceClosed) { 207 return null; 208 } 209 210 FileDescriptor fd = inputPort.claimFileDescriptor(); 211 if (fd == null) { 212 return null; 213 } 214 try { 215 IBinder token = new Binder(); 216 int calleePid = mDeviceServer.connectPorts(token, fd, outputPortNumber); 217 // If the service is a different Process then it will duplicate the fd 218 // and we can safely close this one. 219 // But if the service is in the same Process then closing the fd will 220 // kill the connection. So don't do that. 221 if (calleePid != Process.myPid()) { 222 // close our copy of the file descriptor 223 IoUtils.closeQuietly(fd); 224 } 225 226 return new MidiConnection(token, inputPort); 227 } catch (RemoteException e) { 228 Log.e(TAG, "RemoteException in connectPorts"); 229 return null; 230 } 231 } 232 233 /** 234 * Makes Midi Device available to the Native API 235 * @hide 236 */ 237 public long mirrorToNative() throws IOException { 238 if (mIsDeviceClosed || mNativeHandle != 0) { 239 return 0; 240 } 241 242 mNativeHandle = native_mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId()); 243 if (mNativeHandle == 0) { 244 throw new IOException("Failed mirroring to native"); 245 } 246 247 synchronized (mMirroredDevices) { 248 mMirroredDevices.add(this); 249 } 250 return mNativeHandle; 251 } 252 253 /** 254 * Makes Midi Device no longer available to the Native API 255 * @hide 256 */ 257 public void removeFromNative() { 258 if (mNativeHandle == 0) { 259 return; 260 } 261 262 synchronized (mGuard) { 263 native_removeFromNative(mNativeHandle); 264 mNativeHandle = 0; 265 } 266 267 synchronized (mMirroredDevices) { 268 mMirroredDevices.remove(this); 269 } 270 } 271 272 @Override 273 public void close() throws IOException { 274 synchronized (mGuard) { 275 if (!mIsDeviceClosed) { 276 removeFromNative(); 277 mGuard.close(); 278 mIsDeviceClosed = true; 279 try { 280 mMidiManager.closeDevice(mClientToken, mDeviceToken); 281 } catch (RemoteException e) { 282 Log.e(TAG, "RemoteException in closeDevice"); 283 } 284 } 285 } 286 } 287 288 @Override 289 protected void finalize() throws Throwable { 290 try { 291 if (mGuard != null) { 292 mGuard.warnIfOpen(); 293 } 294 295 close(); 296 } finally { 297 super.finalize(); 298 } 299 } 300 301 @Override 302 public String toString() { 303 return ("MidiDevice: " + mDeviceInfo.toString()); 304 } 305 306 private native long native_mirrorToNative(IBinder deviceServerBinder, int id); 307 private native void native_removeFromNative(long deviceHandle); 308 } 309