blob: 6096355ddb05e246d822d703af1494c4093916cd [file] [log] [blame]
/*
* 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 {
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo; // accessed from native code
private final IMidiDeviceServer mDeviceServer;
private final IBinder mDeviceServerBinder; // accessed from native code
private final IMidiManager mMidiManager;
private final IBinder mClientToken;
private final IBinder mDeviceToken;
private boolean mIsDeviceClosed; // accessed from native code
private long mNativeHandle; // accessed from native code
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 {
if (mGuard != null) {
mGuard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
}
/* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server,
IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) {
mDeviceInfo = deviceInfo;
mDeviceServer = server;
mDeviceServerBinder = mDeviceServer.asBinder();
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;
}
}
@Override
public void close() throws IOException {
synchronized (mGuard) {
// What if there is a native reference to this?
if (mNativeHandle != 0) {
Log.w(TAG, "MidiDevice#close() called while there is an outstanding native client 0x"
+ Long.toHexString(mNativeHandle));
}
if (!mIsDeviceClosed && mNativeHandle == 0) {
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 {
if (mGuard != null) {
mGuard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
@Override
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString());
}
}