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 private static final String TAG = "MidiDevice"; 41 42 private final MidiDeviceInfo mDeviceInfo; // accessed from native code 43 private final IMidiDeviceServer mDeviceServer; 44 private final IBinder mDeviceServerBinder; // accessed from native code 45 private final IMidiManager mMidiManager; 46 private final IBinder mClientToken; 47 private final IBinder mDeviceToken; 48 private boolean mIsDeviceClosed; // accessed from native code 49 50 private long mNativeHandle; // accessed from native code 51 52 private final CloseGuard mGuard = CloseGuard.get(); 53 54 /** 55 * This class represents a connection between the output port of one device 56 * and the input port of another. Created by {@link #connectPorts}. 57 * Close this object to terminate the connection. 58 */ 59 public class MidiConnection implements Closeable { 60 private final IMidiDeviceServer mInputPortDeviceServer; 61 private final IBinder mInputPortToken; 62 private final IBinder mOutputPortToken; 63 private final CloseGuard mGuard = CloseGuard.get(); 64 private boolean mIsClosed; 65 MidiConnection(IBinder outputPortToken, MidiInputPort inputPort)66 MidiConnection(IBinder outputPortToken, MidiInputPort inputPort) { 67 mInputPortDeviceServer = inputPort.getDeviceServer(); 68 mInputPortToken = inputPort.getToken(); 69 mOutputPortToken = outputPortToken; 70 mGuard.open("close"); 71 } 72 73 @Override close()74 public void close() throws IOException { 75 synchronized (mGuard) { 76 if (mIsClosed) return; 77 mGuard.close(); 78 try { 79 // close input port 80 mInputPortDeviceServer.closePort(mInputPortToken); 81 // close output port 82 mDeviceServer.closePort(mOutputPortToken); 83 } catch (RemoteException e) { 84 Log.e(TAG, "RemoteException in MidiConnection.close"); 85 } 86 mIsClosed = true; 87 } 88 } 89 90 @Override finalize()91 protected void finalize() throws Throwable { 92 try { 93 if (mGuard != null) { 94 mGuard.warnIfOpen(); 95 } 96 97 close(); 98 } finally { 99 super.finalize(); 100 } 101 } 102 } 103 MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, IMidiManager midiManager, IBinder clientToken, IBinder deviceToken)104 /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, 105 IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) { 106 mDeviceInfo = deviceInfo; 107 mDeviceServer = server; 108 mDeviceServerBinder = mDeviceServer.asBinder(); 109 mMidiManager = midiManager; 110 mClientToken = clientToken; 111 mDeviceToken = deviceToken; 112 mGuard.open("close"); 113 } 114 115 /** 116 * Returns a {@link MidiDeviceInfo} object, which describes this device. 117 * 118 * @return the {@link MidiDeviceInfo} object 119 */ getInfo()120 public MidiDeviceInfo getInfo() { 121 return mDeviceInfo; 122 } 123 124 /** 125 * Called to open a {@link MidiInputPort} for the specified port number. 126 * 127 * An input port can only be used by one sender at a time. 128 * Opening an input port will fail if another application has already opened it for use. 129 * A {@link MidiDeviceStatus} can be used to determine if an input port is already open. 130 * 131 * @param portNumber the number of the input port to open 132 * @return the {@link MidiInputPort} if the open is successful, 133 * or null in case of failure. 134 */ openInputPort(int portNumber)135 public MidiInputPort openInputPort(int portNumber) { 136 if (mIsDeviceClosed) { 137 return null; 138 } 139 try { 140 IBinder token = new Binder(); 141 FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber); 142 if (fd == null) { 143 return null; 144 } 145 return new MidiInputPort(mDeviceServer, token, fd, portNumber); 146 } catch (RemoteException e) { 147 Log.e(TAG, "RemoteException in openInputPort"); 148 return null; 149 } 150 } 151 152 /** 153 * Called to open a {@link MidiOutputPort} for the specified port number. 154 * 155 * An output port may be opened by multiple applications. 156 * 157 * @param portNumber the number of the output port to open 158 * @return the {@link MidiOutputPort} if the open is successful, 159 * or null in case of failure. 160 */ openOutputPort(int portNumber)161 public MidiOutputPort openOutputPort(int portNumber) { 162 if (mIsDeviceClosed) { 163 return null; 164 } 165 try { 166 IBinder token = new Binder(); 167 FileDescriptor fd = mDeviceServer.openOutputPort(token, portNumber); 168 if (fd == null) { 169 return null; 170 } 171 return new MidiOutputPort(mDeviceServer, token, fd, portNumber); 172 } catch (RemoteException e) { 173 Log.e(TAG, "RemoteException in openOutputPort"); 174 return null; 175 } 176 } 177 178 /** 179 * Connects the supplied {@link MidiInputPort} to the output port of this device 180 * with the specified port number. Once the connection is made, the MidiInput port instance 181 * can no longer receive data via its {@link MidiReceiver#onSend} method. 182 * This method returns a {@link MidiDevice.MidiConnection} object, which can be used 183 * to close the connection. 184 * 185 * @param inputPort the inputPort to connect 186 * @param outputPortNumber the port number of the output port to connect inputPort to. 187 * @return {@link MidiDevice.MidiConnection} object if the connection is successful, 188 * or null in case of failure. 189 */ connectPorts(MidiInputPort inputPort, int outputPortNumber)190 public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) { 191 if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) { 192 throw new IllegalArgumentException("outputPortNumber out of range"); 193 } 194 if (mIsDeviceClosed) { 195 return null; 196 } 197 198 FileDescriptor fd = inputPort.claimFileDescriptor(); 199 if (fd == null) { 200 return null; 201 } 202 try { 203 IBinder token = new Binder(); 204 int calleePid = mDeviceServer.connectPorts(token, fd, outputPortNumber); 205 // If the service is a different Process then it will duplicate the fd 206 // and we can safely close this one. 207 // But if the service is in the same Process then closing the fd will 208 // kill the connection. So don't do that. 209 if (calleePid != Process.myPid()) { 210 // close our copy of the file descriptor 211 IoUtils.closeQuietly(fd); 212 } 213 214 return new MidiConnection(token, inputPort); 215 } catch (RemoteException e) { 216 Log.e(TAG, "RemoteException in connectPorts"); 217 return null; 218 } 219 } 220 221 @Override close()222 public void close() throws IOException { 223 synchronized (mGuard) { 224 // What if there is a native reference to this? 225 if (mNativeHandle != 0) { 226 Log.w(TAG, "MidiDevice#close() called while there is an outstanding native client 0x" 227 + Long.toHexString(mNativeHandle)); 228 } 229 if (!mIsDeviceClosed && mNativeHandle == 0) { 230 mGuard.close(); 231 mIsDeviceClosed = true; 232 try { 233 mMidiManager.closeDevice(mClientToken, mDeviceToken); 234 } catch (RemoteException e) { 235 Log.e(TAG, "RemoteException in closeDevice"); 236 } 237 } 238 } 239 } 240 241 @Override finalize()242 protected void finalize() throws Throwable { 243 try { 244 if (mGuard != null) { 245 mGuard.warnIfOpen(); 246 } 247 248 close(); 249 } finally { 250 super.finalize(); 251 } 252 } 253 254 @Override toString()255 public String toString() { 256 return ("MidiDevice: " + mDeviceInfo.toString()); 257 } 258 } 259