/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media.midi; import android.os.Binder; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.util.Log; import dalvik.system.CloseGuard; import libcore.io.IoUtils; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.util.HashSet; /** * This class is used for sending and receiving data to and from a MIDI device * Instances of this class are created by {@link MidiManager#openDevice}. */ public final class MidiDevice implements Closeable { static { System.loadLibrary("media_jni"); } private static final String TAG = "MidiDevice"; private final MidiDeviceInfo mDeviceInfo; private final IMidiDeviceServer mDeviceServer; private final IMidiManager mMidiManager; private final IBinder mClientToken; private final IBinder mDeviceToken; private boolean mIsDeviceClosed; // Native API Helpers /** * Keep a static list of MidiDevice objects that are mirrorToNative()'d so they * don't get inadvertantly garbage collected. */ private static HashSet mMirroredDevices = new HashSet(); /** * If this device is mirrorToNatived(), this is the native device handler. */ private long mNativeHandle; private final CloseGuard mGuard = CloseGuard.get(); /** * This class represents a connection between the output port of one device * and the input port of another. Created by {@link #connectPorts}. * Close this object to terminate the connection. */ public class MidiConnection implements Closeable { private final IMidiDeviceServer mInputPortDeviceServer; private final IBinder mInputPortToken; private final IBinder mOutputPortToken; private final CloseGuard mGuard = CloseGuard.get(); private boolean mIsClosed; MidiConnection(IBinder outputPortToken, MidiInputPort inputPort) { mInputPortDeviceServer = inputPort.getDeviceServer(); mInputPortToken = inputPort.getToken(); mOutputPortToken = outputPortToken; mGuard.open("close"); } @Override public void close() throws IOException { synchronized (mGuard) { if (mIsClosed) return; mGuard.close(); try { // close input port mInputPortDeviceServer.closePort(mInputPortToken); // close output port mDeviceServer.closePort(mOutputPortToken); } catch (RemoteException e) { Log.e(TAG, "RemoteException in MidiConnection.close"); } mIsClosed = true; } } @Override protected void finalize() throws Throwable { try { mGuard.warnIfOpen(); close(); } finally { super.finalize(); } } } /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) { mDeviceInfo = deviceInfo; mDeviceServer = server; mMidiManager = midiManager; mClientToken = clientToken; mDeviceToken = deviceToken; mGuard.open("close"); } /** * Returns a {@link MidiDeviceInfo} object, which describes this device. * * @return the {@link MidiDeviceInfo} object */ public MidiDeviceInfo getInfo() { return mDeviceInfo; } /** * Called to open a {@link MidiInputPort} for the specified port number. * * An input port can only be used by one sender at a time. * Opening an input port will fail if another application has already opened it for use. * A {@link MidiDeviceStatus} can be used to determine if an input port is already open. * * @param portNumber the number of the input port to open * @return the {@link MidiInputPort} if the open is successful, * or null in case of failure. */ public MidiInputPort openInputPort(int portNumber) { if (mIsDeviceClosed) { return null; } try { IBinder token = new Binder(); FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber); if (fd == null) { return null; } return new MidiInputPort(mDeviceServer, token, fd, portNumber); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openInputPort"); return null; } } /** * Called to open a {@link MidiOutputPort} for the specified port number. * * An output port may be opened by multiple applications. * * @param portNumber the number of the output port to open * @return the {@link MidiOutputPort} if the open is successful, * or null in case of failure. */ public MidiOutputPort openOutputPort(int portNumber) { if (mIsDeviceClosed) { return null; } try { IBinder token = new Binder(); FileDescriptor fd = mDeviceServer.openOutputPort(token, portNumber); if (fd == null) { return null; } return new MidiOutputPort(mDeviceServer, token, fd, portNumber); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openOutputPort"); return null; } } /** * Connects the supplied {@link MidiInputPort} to the output port of this device * with the specified port number. Once the connection is made, the MidiInput port instance * can no longer receive data via its {@link MidiReceiver#onSend} method. * This method returns a {@link MidiDevice.MidiConnection} object, which can be used * to close the connection. * * @param inputPort the inputPort to connect * @param outputPortNumber the port number of the output port to connect inputPort to. * @return {@link MidiDevice.MidiConnection} object if the connection is successful, * or null in case of failure. */ public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) { if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) { throw new IllegalArgumentException("outputPortNumber out of range"); } if (mIsDeviceClosed) { return null; } FileDescriptor fd = inputPort.claimFileDescriptor(); if (fd == null) { return null; } try { IBinder token = new Binder(); int calleePid = mDeviceServer.connectPorts(token, fd, outputPortNumber); // If the service is a different Process then it will duplicate the fd // and we can safely close this one. // But if the service is in the same Process then closing the fd will // kill the connection. So don't do that. if (calleePid != Process.myPid()) { // close our copy of the file descriptor IoUtils.closeQuietly(fd); } return new MidiConnection(token, inputPort); } catch (RemoteException e) { Log.e(TAG, "RemoteException in connectPorts"); return null; } } /** * Makes Midi Device available to the Native API * @hide */ public long mirrorToNative() throws IOException { if (mIsDeviceClosed || mNativeHandle != 0) { return 0; } mNativeHandle = native_mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId()); if (mNativeHandle == 0) { throw new IOException("Failed mirroring to native"); } synchronized (mMirroredDevices) { mMirroredDevices.add(this); } return mNativeHandle; } /** * Makes Midi Device no longer available to the Native API * @hide */ public void removeFromNative() { if (mNativeHandle == 0) { return; } synchronized (mGuard) { native_removeFromNative(mNativeHandle); mNativeHandle = 0; } synchronized (mMirroredDevices) { mMirroredDevices.remove(this); } } @Override public void close() throws IOException { synchronized (mGuard) { if (!mIsDeviceClosed) { removeFromNative(); mGuard.close(); mIsDeviceClosed = true; try { mMidiManager.closeDevice(mClientToken, mDeviceToken); } catch (RemoteException e) { Log.e(TAG, "RemoteException in closeDevice"); } } } } @Override protected void finalize() throws Throwable { try { mGuard.warnIfOpen(); close(); } finally { super.finalize(); } } @Override public String toString() { return ("MidiDevice: " + mDeviceInfo.toString()); } private native long native_mirrorToNative(IBinder deviceServerBinder, int id); private native void native_removeFromNative(long deviceHandle); }