Merge "MidiManager updates:"

This commit is contained in:
Mike Lockwood
2015-01-15 01:32:51 +00:00
committed by Android (Google) Code Review
22 changed files with 1224 additions and 1100 deletions

View File

@@ -174,6 +174,7 @@ LOCAL_SRC_FILES += \
core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/midi/IMidiDeviceServer.aidl \
core/java/android/midi/IMidiListener.aidl \
core/java/android/midi/IMidiManager.aidl \
core/java/android/net/IConnectivityManager.aidl \

View File

@@ -1,11 +1,11 @@
/*
* Copyright (C) 2014, The Android Open Source Project
* 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
* 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,
@@ -16,4 +16,11 @@
package android.midi;
parcelable MidiDevice;
import android.os.ParcelFileDescriptor;
/** @hide */
interface IMidiDeviceServer
{
ParcelFileDescriptor openInputPort(int portNumber);
ParcelFileDescriptor openOutputPort(int portNumber);
}

View File

@@ -16,13 +16,11 @@
package android.midi;
import android.hardware.usb.UsbDevice;
import android.midi.IMidiDeviceServer;
import android.midi.IMidiListener;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
/** @hide */
interface IMidiManager
@@ -34,14 +32,10 @@ interface IMidiManager
void unregisterListener(IBinder token, in IMidiListener listener);
// for communicating with MIDI devices
ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device);
IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
// for implementing virtual MIDI devices
MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
in Bundle properties);
void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device);
// for use by UsbAudioManager
void alsaDeviceAdded(int card, int device, in UsbDevice usbDevice);
void alsaDeviceRemoved(in UsbDevice usbDevice);
MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
void unregisterDeviceServer(in IMidiDeviceServer server);
}

View File

@@ -16,9 +16,8 @@
package android.midi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import java.io.FileDescriptor;
@@ -30,202 +29,22 @@ import java.util.ArrayList;
/**
* This class is used for sending and receiving data to and from an MIDI device
* Instances of this class are created by {@link MidiManager#openDevice}.
* This class can also be used to provide the implementation for a virtual device.
*
* This class implements Parcelable so it can be returned from MidiService when creating
* virtual MIDI devices.
*
* @hide
*/
public final class MidiDevice implements Parcelable {
public final class MidiDevice {
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
private ParcelFileDescriptor mParcelFileDescriptor;
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
// lazily populated lists of ports
private final MidiInputPort[] mInputPorts;
private final MidiOutputPort[] mOutputPorts;
// array of receiver lists, indexed by port number
private final ArrayList<MidiReceiver>[] mReceivers;
private int mReceiverCount; // total number of receivers for all ports
/**
* Minimum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
public static final int MIN_PACKED_MESSAGE_SIZE = 10;
/**
* Maximum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
public static final int MAX_PACKED_MESSAGE_SIZE = 12;
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
private final Thread mThread = new Thread() {
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
// read next event
int count = mInputStream.read(buffer);
if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
Log.e(TAG, "Number of bytes read out of range: " + count);
break;
}
int offset = getMessageOffset(buffer, count);
int size = getMessageSize(buffer, count);
long timestamp = getMessageTimeStamp(buffer, count);
int port = getMessagePortNumber(buffer, count);
synchronized (mReceivers) {
ArrayList<MidiReceiver> receivers = mReceivers[port];
if (receivers != null) {
for (int i = 0; i < receivers.size(); i++) {
MidiReceiver receiver = receivers.get(i);
try {
receivers.get(i).onPost(buffer, offset, size, timestamp);
} catch (IOException e) {
Log.e(TAG, "post failed");
deadReceivers.add(receiver);
}
}
// remove any receivers that failed
if (deadReceivers.size() > 0) {
for (MidiReceiver receiver: deadReceivers) {
receivers.remove(receiver);
mReceiverCount--;
}
deadReceivers.clear();
}
if (receivers.size() == 0) {
mReceivers[port] = null;
}
// exit if we have no receivers left
if (mReceiverCount == 0) {
break;
}
}
}
}
} catch (IOException e) {
Log.e(TAG, "read failed");
}
}
};
private final IMidiDeviceServer mServer;
/**
* MidiDevice should only be instantiated by MidiManager or MidiService
* MidiDevice should only be instantiated by MidiManager
* @hide
*/
public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
mDeviceInfo = deviceInfo;
mParcelFileDescriptor = pfd;
int inputPorts = deviceInfo.getInputPortCount();
int outputPorts = deviceInfo.getOutputPortCount();
mInputPorts = new MidiInputPort[inputPorts];
mOutputPorts = new MidiOutputPort[outputPorts];
mReceivers = new ArrayList[outputPorts];
}
/**
* Called to open a {@link MidiInputPort} for the specified port number.
*
* @param portNumber the number of the input port to open
* @return the {@link MidiInputPort}
*/
public MidiInputPort openInputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
throw new IllegalArgumentException("input port number out of range");
}
synchronized (mInputPorts) {
if (mInputPorts[portNumber] == null) {
mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber);
}
return mInputPorts[portNumber];
}
}
/**
* Called to open a {@link MidiOutputPort} for the specified port number.
*
* @param portNumber the number of the output port to open
* @return the {@link MidiOutputPort}
*/
public MidiOutputPort openOutputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
throw new IllegalArgumentException("output port number out of range");
}
synchronized (mOutputPorts) {
if (mOutputPorts[portNumber] == null) {
mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber);
}
return mOutputPorts[portNumber];
}
}
/* package */ void connect(MidiReceiver receiver, int portNumber) {
synchronized (mReceivers) {
if (mReceivers[portNumber] == null) {
mReceivers[portNumber] = new ArrayList<MidiReceiver>();
}
mReceivers[portNumber].add(receiver);
if (mReceiverCount++ == 0) {
mThread.start();
}
}
}
/* package */ void disconnect(MidiReceiver receiver, int portNumber) {
synchronized (mReceivers) {
ArrayList<MidiReceiver> receivers = mReceivers[portNumber];
if (receivers != null && receivers.remove(receiver)) {
mReceiverCount--;
}
}
}
/* package */ boolean open() {
FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
try {
mInputStream = new FileInputStream(fd);
} catch (Exception e) {
Log.e(TAG, "could not create mInputStream", e);
return false;
}
try {
mOutputStream = new FileOutputStream(fd);
} catch (Exception e) {
Log.e(TAG, "could not create mOutputStream", e);
return false;
}
return true;
}
/* package */ void close() {
try {
if (mInputStream != null) {
mInputStream.close();
}
if (mOutputStream != null) {
mOutputStream.close();
}
mParcelFileDescriptor.close();
} catch (IOException e) {
}
mServer = server;
}
/**
@@ -237,113 +56,46 @@ public final class MidiDevice implements Parcelable {
return mDeviceInfo;
}
@Override
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
/**
* Called to open a {@link MidiInputPort} for the specified port number.
*
* @param portNumber the number of the input port to open
* @return the {@link MidiInputPort}
*/
public MidiInputPort openInputPort(int portNumber) {
try {
ParcelFileDescriptor pfd = mServer.openInputPort(portNumber);
if (pfd == null) {
return null;
}
return new MidiInputPort(pfd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openInputPort");
return null;
}
}
public static final Parcelable.Creator<MidiDevice> CREATOR =
new Parcelable.Creator<MidiDevice>() {
public MidiDevice createFromParcel(Parcel in) {
MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null);
ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null);
return new MidiDevice(deviceInfo, pfd);
/**
* Called to open a {@link MidiOutputPort} for the specified port number.
*
* @param portNumber the number of the output port to open
* @return the {@link MidiOutputPort}
*/
public MidiOutputPort openOutputPort(int portNumber) {
try {
ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber);
if (pfd == null) {
return null;
}
return new MidiOutputPort(pfd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openOutputPort");
return null;
}
public MidiDevice[] newArray(int size) {
return new MidiDevice[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mDeviceInfo, flags);
parcel.writeParcelable(mParcelFileDescriptor, flags);
}
/**
* Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
* timestamp is message timestamp to pack
* dest is buffer to pack into
* returns size of packed message
*
* @hide
*/
public static int packMessage(byte[] message, int offset, int size, long timestamp,
int portNumber, byte[] dest) {
// pack variable length message first
System.arraycopy(message, offset, dest, 0, size);
int destOffset = size;
// timestamp takes 8 bytes
for (int i = 0; i < 8; i++) {
dest[destOffset++] = (byte)timestamp;
timestamp >>= 8;
}
// portNumber is last
dest[destOffset++] = (byte)portNumber;
return destOffset;
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString());
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns the offet of of MIDI message in packed buffer
*
* @hide
*/
public static int getMessageOffset(byte[] buffer, int bufferLength) {
// message is at start of buffer
return 0;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns size of MIDI message in packed buffer
*
* @hide
*/
public static int getMessageSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp and port number
return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*
* @hide
*/
public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
long timestamp = 0;
// timestamp follows variable length message data
int dataLength = getMessageSize(buffer, bufferLength);
for (int i = dataLength + 7; i >= dataLength; i--) {
// why can't Java deal with unsigned ints?
int b = buffer[i];
if (b < 0) b += 256;
timestamp = (timestamp << 8) | b;
}
return timestamp;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* unpacks port number from packed buffer
*
* @hide
*/
public static int getMessagePortNumber(byte[] buffer, int bufferLength) {
// timestamp follows variable length message data and timestamp
int dataLength = getMessageSize(buffer, bufferLength);
return buffer[dataLength + 8 /* sizeof(timestamp) */];
}
}

View File

@@ -50,10 +50,6 @@ public class MidiDeviceInfo implements Parcelable {
private final int mOutputPortCount;
private final Bundle mProperties;
// used for USB devices only
private final int mAlsaCard;
private final int mAlsaDevice;
/**
* Bundle key for the device's manufacturer name property.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
@@ -82,6 +78,20 @@ public class MidiDeviceInfo implements Parcelable {
*/
public static final String PROPERTY_USB_DEVICE = "usb_device";
/**
* Bundle key for the device's ALSA card number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
*/
public static final String PROPERTY_ALSA_CARD = "alsa_card";
/**
* Bundle key for the device's ALSA device number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
*/
public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
/**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
@@ -93,23 +103,6 @@ public class MidiDeviceInfo implements Parcelable {
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
mProperties = properties;
mAlsaCard = -1;
mAlsaDevice = -1;
}
/**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
Bundle properties, int alsaCard, int alsaDevice) {
mType = type;
mId = id;
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
mProperties = properties;
mAlsaCard = alsaCard;
mAlsaDevice = alsaDevice;
}
/**
@@ -158,20 +151,6 @@ public class MidiDeviceInfo implements Parcelable {
return mProperties;
}
/**
* @hide
*/
public int getAlsaCard() {
return mAlsaCard;
}
/**
* @hide
*/
public int getAlsaDevice() {
return mAlsaDevice;
}
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -191,9 +170,7 @@ public class MidiDeviceInfo implements Parcelable {
return ("MidiDeviceInfo[mType=" + mType +
",mInputPortCount=" + mInputPortCount +
",mOutputPortCount=" + mOutputPortCount +
",mProperties=" + mProperties +
",mAlsaCard=" + mAlsaCard +
",mAlsaDevice=" + mAlsaDevice);
",mProperties=" + mProperties);
}
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -204,9 +181,7 @@ public class MidiDeviceInfo implements Parcelable {
int inputPorts = in.readInt();
int outputPorts = in.readInt();
Bundle properties = in.readBundle();
int card = in.readInt();
int device = in.readInt();
return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, card, device);
return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -224,7 +199,5 @@ public class MidiDeviceInfo implements Parcelable {
parcel.writeInt(mInputPortCount);
parcel.writeInt(mOutputPortCount);
parcel.writeBundle(mProperties);
parcel.writeInt(mAlsaCard);
parcel.writeInt(mAlsaDevice);
}
}

View File

@@ -0,0 +1,264 @@
/*
* 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.midi;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
/** @hide */
public final class MidiDeviceServer implements Closeable {
private static final String TAG = "MidiDeviceServer";
private final IMidiManager mMidiManager;
// MidiDeviceInfo for the device implemented by this server
private MidiDeviceInfo mDeviceInfo;
private int mInputPortCount;
private int mOutputPortCount;
// output ports for receiving messages from our clients
// we can have only one per port number
private MidiOutputPort[] mInputPortSenders;
// receivers attached to our input ports
private ArrayList<MidiReceiver>[] mInputPortReceivers;
// input ports for sending messages to our clients
// we can have multiple outputs per port number
private ArrayList<MidiInputPort>[] mOutputPortReceivers;
// subclass of MidiInputPort for passing to clients
// that notifies us when the connection has failed
private class ServerInputPort extends MidiInputPort {
ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
super(pfd, portNumber);
}
@Override
public void onIOException() {
synchronized (mOutputPortReceivers) {
mOutputPortReceivers[getPortNumber()] = null;
}
}
}
// subclass of MidiOutputPort for passing to clients
// that notifies us when the connection has failed
private class ServerOutputPort extends MidiOutputPort {
ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
super(pfd, portNumber);
}
@Override
public void onIOException() {
synchronized (mInputPortSenders) {
mInputPortSenders[getPortNumber()] = null;
}
}
}
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@Override
public ParcelFileDescriptor openInputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mInputPortCount) {
Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
return null;
}
ParcelFileDescriptor result = null;
MidiOutputPort newOutputPort = null;
synchronized (mInputPortSenders) {
if (mInputPortSenders[portNumber] != null) {
Log.d(TAG, "port " + portNumber + " already open");
return null;
}
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
newOutputPort = new ServerOutputPort(pair[0], portNumber);
mInputPortSenders[portNumber] = newOutputPort;
result = pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
return null;
}
if (newOutputPort != null) {
ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
synchronized (receivers) {
for (int i = 0; i < receivers.size(); i++) {
newOutputPort.connect(receivers.get(i));
}
}
}
}
return result;
}
@Override
public ParcelFileDescriptor openOutputPort(int portNumber) {
if (portNumber < 0 || portNumber >= mOutputPortCount) {
Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
return null;
}
synchronized (mOutputPortReceivers) {
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
return null;
}
}
}
};
/* package */ MidiDeviceServer(IMidiManager midiManager) {
mMidiManager = midiManager;
}
/* package */ IMidiDeviceServer getBinderInterface() {
return mServer;
}
/* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
if (mDeviceInfo != null) {
throw new IllegalStateException("setDeviceInfo should only be called once");
}
mDeviceInfo = deviceInfo;
mInputPortCount = deviceInfo.getInputPortCount();
mOutputPortCount = deviceInfo.getOutputPortCount();
mInputPortSenders = new MidiOutputPort[mInputPortCount];
mInputPortReceivers = new ArrayList[mInputPortCount];
for (int i = 0; i < mInputPortCount; i++) {
mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
}
mOutputPortReceivers = new ArrayList[mOutputPortCount];
for (int i = 0; i < mOutputPortCount; i++) {
mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
}
}
@Override
public void close() throws IOException {
try {
// FIXME - close input and output ports too?
mMidiManager.unregisterDeviceServer(mServer);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in unregisterDeviceServer");
}
}
/**
* Returns a {@link MidiDeviceInfo} object, which describes this device.
*
* @return the {@link MidiDeviceInfo} object
*/
public MidiDeviceInfo getInfo() {
return mDeviceInfo;
}
/**
* Called to open a {@link MidiSender} to allow receiving MIDI messages
* on the device's input port for the specified port number.
*
* @param portNumber the number of the input port
* @return the {@link MidiSender}
*/
public MidiSender openInputPortSender(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
}
final int portNumberF = portNumber;
return new MidiSender() {
@Override
public void connect(MidiReceiver receiver) {
// We always synchronize on mInputPortSenders before receivers if we need to
// synchronize on both.
synchronized (mInputPortSenders) {
ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
synchronized (receivers) {
receivers.add(receiver);
MidiOutputPort outputPort = mInputPortSenders[portNumberF];
if (outputPort != null) {
outputPort.connect(receiver);
}
}
}
}
@Override
public void disconnect(MidiReceiver receiver) {
// We always synchronize on mInputPortSenders before receivers if we need to
// synchronize on both.
synchronized (mInputPortSenders) {
ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
synchronized (receivers) {
receivers.remove(receiver);
MidiOutputPort outputPort = mInputPortSenders[portNumberF];
if (outputPort != null) {
outputPort.disconnect(receiver);
}
}
}
}
};
}
/**
* Called to open a {@link MidiReceiver} to allow sending MIDI messages
* on the virtual device's output port for the specified port number.
*
* @param portNumber the number of the output port
* @return the {@link MidiReceiver}
*/
public MidiReceiver openOutputPortReceiver(int portNumber) {
if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
}
final int portNumberF = portNumber;
return new MidiReceiver() {
@Override
public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
synchronized (receivers) {
for (int i = 0; i < receivers.size(); i++) {
// FIXME catch errors and remove dead ones
receivers.get(i).onPost(msg, offset, count, timestamp);
}
}
}
};
}
}

View File

@@ -16,6 +16,10 @@
package android.midi;
import android.os.ParcelFileDescriptor;
import libcore.io.IoUtils;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -24,15 +28,16 @@ import java.io.IOException;
*
* @hide
*/
public final class MidiInputPort extends MidiPort implements MidiReceiver {
public class MidiInputPort extends MidiPort implements MidiReceiver {
private final FileOutputStream mOutputStream;
// buffer to use for sending messages out our output stream
private final byte[] mBuffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
/* package */ MidiInputPort(FileOutputStream outputStream, int portNumber) {
// buffer to use for sending messages out our output stream
private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE];
/* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
mOutputStream = outputStream;
mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
}
/**
@@ -46,9 +51,20 @@ public final class MidiInputPort extends MidiPort implements MidiReceiver {
*/
public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
synchronized (mBuffer) {
int length = MidiDevice.packMessage(msg, offset, count, timestamp, getPortNumber(),
mBuffer);
mOutputStream.write(mBuffer, 0, length);
int length = packMessage(msg, offset, count, timestamp, mBuffer);
try {
mOutputStream.write(mBuffer, 0, length);
} catch (IOException e) {
IoUtils.closeQuietly(mOutputStream);
// report I/O failure
onIOException();
throw e;
}
}
}
@Override
public void close() throws IOException {
mOutputStream.close();
}
}

View File

@@ -20,7 +20,6 @@ import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
@@ -146,40 +145,31 @@ public class MidiManager {
*/
public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
try {
ParcelFileDescriptor pfd = mService.openDevice(mToken, deviceInfo);
if (pfd == null) {
IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
if (server == null) {
Log.e(TAG, "could not open device " + deviceInfo);
return null;
}
MidiDevice device = new MidiDevice(deviceInfo, pfd);
if (device.open()) {
Log.d(TAG, "openDevice returning " + device);
return device;
}
return new MidiDevice(deviceInfo, server);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openDevice");
}
return null;
}
/**
* Creates a new MIDI virtual device.
* NOTE: The method for creating virtual devices is likely to change before release.
*
* @param numInputPorts number of input ports for the virtual device
* @param numOutputPorts number of output ports for the virtual device
* @param properties a {@link android.os.Bundle} containing properties describing the device
* @return a {@link MidiDevice} object to locally represent the device
*/
public MidiDevice createVirtualDevice(int numInputPorts, int numOutputPorts,
Bundle properties) {
/** @hide */
public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
Bundle properties, boolean isPrivate, int type) {
try {
MidiDevice device = mService.registerVirtualDevice(mToken,
numInputPorts, numOutputPorts, properties);
if (device != null && !device.open()) {
device = null;
MidiDeviceServer server = new MidiDeviceServer(mService);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
numInputPorts, numOutputPorts, properties, isPrivate, type);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
}
return device;
server.setDeviceInfo(deviceInfo);
return server;
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in createVirtualDevice");
return null;
@@ -187,16 +177,19 @@ public class MidiManager {
}
/**
* Removes a MIDI virtual device.
* Creates a new MIDI virtual device.
*
* @param device the {@link MidiDevice} for the virtual device to remove
* @param numInputPorts number of input ports for the virtual device
* @param numOutputPorts number of output ports for the virtual device
* @param properties a {@link android.os.Bundle} containing properties describing the device
* @param isPrivate true if this device should only be visible and accessible to apps
* with the same UID as the caller
* @return a {@link MidiVirtualDevice} object to locally represent the device
*/
public void closeVirtualDevice(MidiDevice device) {
try {
device.close();
mService.unregisterVirtualDevice(mToken, device.getInfo());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in unregisterVirtualDevice");
}
public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
Bundle properties, boolean isPrivate) {
return createDeviceServer(numInputPorts, numOutputPorts, properties,
isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
}
}

View File

@@ -16,20 +16,88 @@
package android.midi;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* This class is used for receiving data to a port on a MIDI device
*
* @hide
*/
public final class MidiOutputPort extends MidiPort implements MidiSender {
public class MidiOutputPort extends MidiPort implements MidiSender {
private static final String TAG = "MidiOutputPort";
private final MidiDevice mDevice;
private final FileInputStream mInputStream;
/* package */ MidiOutputPort(MidiDevice device, int portNumber) {
// array of receiver lists, indexed by port number
private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
private int mReceiverCount; // total number of receivers for all ports
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
private final Thread mThread = new Thread() {
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
// read next event
int count = mInputStream.read(buffer);
if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
Log.e(TAG, "Number of bytes read out of range: " + count);
break;
}
int offset = getMessageOffset(buffer, count);
int size = getMessageSize(buffer, count);
long timestamp = getMessageTimeStamp(buffer, count);
synchronized (mReceivers) {
for (int i = 0; i < mReceivers.size(); i++) {
MidiReceiver receiver = mReceivers.get(i);
try {
receiver.onPost(buffer, offset, size, timestamp);
} catch (IOException e) {
Log.e(TAG, "post failed");
deadReceivers.add(receiver);
}
}
// remove any receivers that failed
if (deadReceivers.size() > 0) {
for (MidiReceiver receiver: deadReceivers) {
mReceivers.remove(receiver);
mReceiverCount--;
}
deadReceivers.clear();
}
// exit if we have no receivers left
if (mReceiverCount == 0) {
break;
}
}
}
} catch (IOException e) {
Log.e(TAG, "read failed");
// report I/O failure
IoUtils.closeQuietly(mInputStream);
onIOException();
}
}
};
/* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
mDevice = device;
mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
}
/**
@@ -39,7 +107,12 @@ public final class MidiOutputPort extends MidiPort implements MidiSender {
* @param receiver the receiver to connect
*/
public void connect(MidiReceiver receiver) {
mDevice.connect(receiver, getPortNumber());
synchronized (mReceivers) {
mReceivers.add(receiver);
if (mReceiverCount++ == 0) {
mThread.start();
}
}
}
/**
@@ -48,6 +121,15 @@ public final class MidiOutputPort extends MidiPort implements MidiSender {
* @param receiver the receiver to connect
*/
public void disconnect(MidiReceiver receiver) {
mDevice.disconnect(receiver, getPortNumber());
synchronized (mReceivers) {
if (mReceivers.remove(receiver)) {
mReceiverCount--;
}
}
}
@Override
public void close() throws IOException {
mInputStream.close();
}
}

View File

@@ -16,8 +16,9 @@
package android.midi;
import java.io.FileOutputStream;
import java.io.IOException;
import android.util.Log;
import java.io.Closeable;
/**
* This class represents a MIDI input or output port.
@@ -25,10 +26,24 @@ import java.io.IOException;
*
* @hide
*/
public class MidiPort {
abstract public class MidiPort implements Closeable {
private static final String TAG = "MidiPort";
private final int mPortNumber;
/**
* Minimum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
*/
protected static final int MIN_PACKED_MESSAGE_SIZE = 10;
/**
* Maximum size of packed message as sent through our ParcelFileDescriptor
* 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
*/
protected static final int MAX_PACKED_MESSAGE_SIZE = 12;
/* package */ MidiPort(int portNumber) {
mPortNumber = portNumber;
}
@@ -41,4 +56,69 @@ public class MidiPort {
public final int getPortNumber() {
return mPortNumber;
}
/**
* Called when an IOExeption occurs while sending or receiving data.
* Subclasses can override to be notified of such errors
*
*/
public void onIOException() {
}
/**
* Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
* timestamp is message timestamp to pack
* dest is buffer to pack into
* returns size of packed message
*/
protected static int packMessage(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
// pack variable length message first
System.arraycopy(message, offset, dest, 0, size);
int destOffset = size;
// timestamp takes 8 bytes
for (int i = 0; i < 8; i++) {
dest[destOffset++] = (byte)timestamp;
timestamp >>= 8;
}
return destOffset;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns the offet of of MIDI message in packed buffer
*/
protected static int getMessageOffset(byte[] buffer, int bufferLength) {
// message is at start of buffer
return 0;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* returns size of MIDI message in packed buffer
*/
protected static int getMessageSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp and port number
return bufferLength - 8 /* sizeof(timestamp) */;
}
/**
* Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*/
protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
long timestamp = 0;
// timestamp follows variable length message data
int dataLength = getMessageSize(buffer, bufferLength);
for (int i = dataLength + 7; i >= dataLength; i--) {
int b = (int)buffer[i] & 0xFF;
timestamp = (timestamp << 8) | b;
}
return timestamp;
}
}

View File

@@ -0,0 +1,306 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server;
import android.content.Context;
import android.midi.IMidiDeviceServer;
import android.midi.IMidiListener;
import android.midi.IMidiManager;
import android.midi.MidiDeviceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
public class MidiService extends IMidiManager.Stub {
private static final String TAG = "MidiService";
private final Context mContext;
// list of all our clients, keyed by Binder token
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
// list of all devices, keyed by MidiDeviceInfo
private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
= new HashMap<MidiDeviceInfo, Device>();
// list of all devices, keyed by IMidiDeviceServer
private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
// used for assigning IDs to MIDI devices
private int mNextDeviceId = 1;
private final class Client implements IBinder.DeathRecipient {
// Binder token for this client
private final IBinder mToken;
// This client's UID
private final int mUid;
// This client's PID
private final int mPid;
// List of all receivers for this client
private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
public Client(IBinder token) {
mToken = token;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
}
public int getUid() {
return mUid;
}
public void addListener(IMidiListener listener) {
mListeners.add(listener);
}
public void removeListener(IMidiListener listener) {
mListeners.remove(listener);
if (mListeners.size() == 0) {
removeClient(mToken);
}
}
public void deviceAdded(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceAdded(deviceInfo);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void deviceRemoved(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceRemoved(deviceInfo);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void binderDied() {
removeClient(mToken);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Client: UID: ");
sb.append(mUid);
sb.append(" PID: ");
sb.append(mPid);
sb.append(" listener count: ");
sb.append(mListeners.size());
return sb.toString();
}
}
private Client getClient(IBinder token) {
synchronized (mClients) {
Client client = mClients.get(token);
if (client == null) {
client = new Client(token);
try {
token.linkToDeath(client, 0);
} catch (RemoteException e) {
return null;
}
mClients.put(token, client);
}
return client;
}
}
private void removeClient(IBinder token) {
mClients.remove(token);
}
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
// UID of device creator
private final int mUid;
// PID of device creator
private final int mPid;
public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
mServer = server;
mDeviceInfo = deviceInfo;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
}
public MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
}
public IMidiDeviceServer getDeviceServer() {
return mServer;
}
public boolean isUidAllowed(int uid) {
// FIXME
return true;
}
public void binderDied() {
synchronized (mDevicesByServer) {
removeDeviceLocked(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Device: ");
sb.append(mDeviceInfo);
sb.append(" UID: ");
sb.append(mUid);
sb.append(" PID: ");
sb.append(mPid);
return sb.toString();
}
}
public MidiService(Context context) {
mContext = context;
}
public void registerListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
}
public void unregisterListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.removeListener(listener);
}
public MidiDeviceInfo[] getDeviceList() {
return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
}
public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
Device device = mDevicesByInfo.get(deviceInfo);
if (device == null) {
Log.e(TAG, "device not found in openDevice: " + deviceInfo);
return null;
}
if (!device.isUidAllowed(Binder.getCallingUid())) {
throw new SecurityException("Attempt to open private device with wrong UID");
}
return device.getDeviceServer();
}
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("only system can create non-virtual devices");
}
MidiDeviceInfo deviceInfo;
Device device;
synchronized (mDevicesByServer) {
int id = mNextDeviceId++;
deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
IBinder binder = server.asBinder();
device = new Device(server, deviceInfo);
try {
binder.linkToDeath(device, 0);
} catch (RemoteException e) {
return null;
}
mDevicesByInfo.put(deviceInfo, device);
mDevicesByServer.put(server.asBinder(), device);
}
synchronized (mClients) {
for (Client c : mClients.values()) {
c.deviceAdded(device);
}
}
return deviceInfo;
}
public void unregisterDeviceServer(IMidiDeviceServer server) {
synchronized (mDevicesByServer) {
removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
}
}
// synchronize on mDevicesByServer
private void removeDeviceLocked(Device device) {
if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
mDevicesByInfo.remove(device.getDeviceInfo());
synchronized (mClients) {
for (Client c : mClients.values()) {
c.deviceRemoved(device);
}
}
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("MIDI Manager State:");
pw.increaseIndent();
pw.println("Devices:");
pw.increaseIndent();
for (Device device : mDevicesByInfo.values()) {
pw.println(device.toString());
}
pw.decreaseIndent();
pw.println("Clients:");
pw.increaseIndent();
for (Client client : mClients.values()) {
pw.println(client.toString());
}
pw.decreaseIndent();
}
}

View File

@@ -1,170 +0,0 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server.midi;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* Abstract internal base class for entities in MidiService.
* This class contains two threads for reading and writing MIDI events.
* On one end we have the readMessage() and writeMessage() methods, which must be
* implemented by a subclass. On the other end we have file descriptors for sockets
* attached to client applications.
*/
abstract class MidiDeviceBase {
private static final String TAG = "MidiDeviceBase";
final MidiDeviceInfo mDeviceInfo;
private ReaderThread mReaderThread;
private WriterThread mWriterThread;
private ParcelFileDescriptor mParcelFileDescriptor;
// Reads MIDI messages from readMessage() and write them to one or more clients.
private class ReaderThread extends Thread {
private final ArrayList<FileOutputStream> mOutputStreams =
new ArrayList<FileOutputStream>();
@Override
public void run() {
byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
while (true) {
try {
int count = readMessage(buffer);
if (count > 0) {
synchronized (mOutputStreams) {
for (int i = 0; i < mOutputStreams.size(); i++) {
FileOutputStream fos = mOutputStreams.get(i);
try {
fos.write(buffer, 0, count);
} catch (IOException e) {
Log.e(TAG, "write failed", e);
mOutputStreams.remove(fos);
}
}
}
}
} catch (IOException e) {
Log.e(TAG, "read failed", e);
break;
}
}
}
public void addListener(FileOutputStream fos) {
synchronized (mOutputStreams) {
mOutputStreams.add(fos);
}
}
public void removeListener(FileOutputStream fos) {
synchronized (mOutputStreams) {
mOutputStreams.remove(fos);
}
}
}
// Reads MIDI messages from our client and writes them by calling writeMessage()
private class WriterThread extends Thread {
private final FileInputStream mInputStream;
public WriterThread(FileInputStream fis) {
mInputStream = fis;
}
@Override
public void run() {
byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
while (true) {
try {
int count = mInputStream.read(buffer);
writeMessage(buffer, count);
} catch (IOException e) {
Log.e(TAG, "WriterThread failed", e);
break;
}
}
}
}
public MidiDeviceBase(MidiDeviceInfo info) {
mDeviceInfo = info;
}
public MidiDeviceInfo getInfo() {
return mDeviceInfo;
}
public ParcelFileDescriptor getFileDescriptor() {
synchronized (this) {
if (mReaderThread == null) {
if (!open()) {
return null;
}
mReaderThread = new ReaderThread();
mReaderThread.start();
}
}
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
mParcelFileDescriptor = pair[0];
FileOutputStream fos = new FileOutputStream(mParcelFileDescriptor.getFileDescriptor());
mReaderThread.addListener(fos);
// return an error if the device is already open for writing?
if (mWriterThread == null) {
FileInputStream fis = new FileInputStream(
mParcelFileDescriptor.getFileDescriptor());
mWriterThread = new WriterThread(fis);
mWriterThread.start();
}
return pair[1];
} catch (IOException e) {
Log.e(TAG, "could not create ParcelFileDescriptor pair", e);
return null;
}
}
abstract boolean open();
void close() {
try {
if (mParcelFileDescriptor != null) {
mParcelFileDescriptor.close();
}
} catch (IOException e) {
}
}
abstract int readMessage(byte[] buffer) throws IOException;
abstract void writeMessage(byte[] buffer, int count) throws IOException;
}

View File

@@ -1,283 +0,0 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server.midi;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.midi.IMidiListener;
import android.midi.IMidiManager;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
public class MidiService extends IMidiManager.Stub {
private static final String TAG = "MidiService";
private final Context mContext;
// list of all our clients, keyed by Binder token
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
// list of all devices, keyed by ID
private final HashMap<Integer, MidiDeviceBase> mDevices
= new HashMap<Integer, MidiDeviceBase>();
// list of all USB devices, keyed by USB device.
private final HashMap<UsbDevice, UsbMidiDevice> mUsbDevices
= new HashMap<UsbDevice, UsbMidiDevice>();
// used for assigning IDs to MIDI devices
private int mNextDeviceId = 1;
private final class Client implements IBinder.DeathRecipient {
private final IBinder mToken;
private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
private final ArrayList<MidiDeviceBase> mVirtualDevices = new ArrayList<MidiDeviceBase>();
public Client(IBinder token) {
mToken = token;
}
public void close() {
for (MidiDeviceBase device : mVirtualDevices) {
device.close();
}
mVirtualDevices.clear();
}
public void addListener(IMidiListener listener) {
mListeners.add(listener);
}
public void removeListener(IMidiListener listener) {
mListeners.remove(listener);
if (mListeners.size() == 0 && mVirtualDevices.size() == 0) {
removeClient(mToken);
}
}
public void addVirtualDevice(MidiDeviceBase device) {
mVirtualDevices.add(device);
}
public void removeVirtualDevice(MidiDeviceBase device) {
mVirtualDevices.remove(device);
}
public void deviceAdded(MidiDeviceInfo device) {
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceAdded(device);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void deviceRemoved(MidiDeviceInfo device) {
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceRemoved(device);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void binderDied() {
removeClient(mToken);
}
}
private Client getClient(IBinder token) {
synchronized (mClients) {
Client client = mClients.get(token);
if (client == null) {
client = new Client(token);
try {
token.linkToDeath(client, 0);
} catch (RemoteException e) {
return null;
}
mClients.put(token, client);
}
return client;
}
}
private void removeClient(IBinder token) {
synchronized (mClients) {
Client client = mClients.remove(token);
if (client != null) {
client.close();
}
}
}
public MidiService(Context context) {
mContext = context;
}
public void registerListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
}
public void unregisterListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.removeListener(listener);
}
public MidiDeviceInfo[] getDeviceList() {
ArrayList<MidiDeviceInfo> infos = new ArrayList<MidiDeviceInfo>();
for (MidiDeviceBase device : mDevices.values()) {
infos.add(device.getInfo());
}
return infos.toArray(new MidiDeviceInfo[0]);
}
public ParcelFileDescriptor openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
MidiDeviceBase device = mDevices.get(deviceInfo.getId());
if (device == null) {
Log.e(TAG, "device not found in openDevice: " + deviceInfo);
return null;
}
return device.getFileDescriptor();
}
public MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
Bundle properties) {
VirtualMidiDevice device;
Client client = getClient(token);
if (client == null) return null;
synchronized (mDevices) {
int id = mNextDeviceId++;
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_VIRTUAL, id,
numInputPorts, numOutputPorts, properties);
device = new VirtualMidiDevice(deviceInfo);
if (!device.open()) {
return null;
}
mDevices.put(id, device);
client.addVirtualDevice(device);
}
synchronized (mClients) {
MidiDeviceInfo deviceInfo = device.getInfo();
for (Client c : mClients.values()) {
c.deviceAdded(deviceInfo);
}
}
return device.getProxy();
}
public void unregisterVirtualDevice(IBinder token, MidiDeviceInfo deviceInfo) {
Client client = getClient(token);
if (client == null) return;
MidiDeviceBase device;
synchronized (mDevices) {
device = mDevices.remove(deviceInfo.getId());
}
if (device != null) {
client.removeVirtualDevice(device);
device.close();
synchronized (mClients) {
for (Client c : mClients.values()) {
c.deviceRemoved(deviceInfo);
}
}
}
}
// called by UsbAudioManager to notify of new USB MIDI devices
public void alsaDeviceAdded(int card, int device, UsbDevice usbDevice) {
Log.d(TAG, "alsaDeviceAdded: card:" + card + " device:" + device);
MidiDeviceInfo deviceInfo;
synchronized (mDevices) {
int id = mNextDeviceId++;
Bundle properties = new Bundle();
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER,
usbDevice.getManufacturerName());
properties.putString(MidiDeviceInfo.PROPERTY_MODEL,
usbDevice.getProductName());
properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
usbDevice.getSerialNumber());
properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
// FIXME - multiple ports not supported yet
int inputPorts = 1;
int outputPorts = 1;
deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, inputPorts, outputPorts,
properties, card, device);
UsbMidiDevice midiDevice = new UsbMidiDevice(deviceInfo);
mDevices.put(id, midiDevice);
mUsbDevices.put(usbDevice, midiDevice);
}
synchronized (mClients) {
for (Client client : mClients.values()) {
client.deviceAdded(deviceInfo);
}
}
}
// called by UsbAudioManager to notify of removed USB MIDI devices
public void alsaDeviceRemoved(UsbDevice usbDevice) {
MidiDeviceInfo deviceInfo = null;
synchronized (mDevices) {
MidiDeviceBase device = mUsbDevices.remove(usbDevice);
if (device != null) {
device.close();
deviceInfo = device.getInfo();
mDevices.remove(deviceInfo.getId());
}
}
Log.d(TAG, "alsaDeviceRemoved: " + deviceInfo);
if (deviceInfo != null) {
synchronized (mClients) {
for (Client client : mClients.values()) {
client.deviceRemoved(deviceInfo);
}
}
}
}
}

View File

@@ -1,116 +0,0 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server.midi;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.midi.MidiUtils;
import android.os.Binder;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// This is our subclass of MidiDeviceBase for communicating with USB MIDI devices
// via the ALSA driver file system.
class UsbMidiDevice extends MidiDeviceBase {
private static final String TAG = "UsbMidiDevice";
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
private final byte[] mBuffer = new byte[3];
public UsbMidiDevice(MidiDeviceInfo info) {
super(info);
}
public boolean open() {
if (mInputStream != null && mOutputStream != null) {
// already open
return true;
}
int card = mDeviceInfo.getAlsaCard();
int device = mDeviceInfo.getAlsaDevice();
if (card == -1 || device == -1) {
Log.e(TAG, "Not a USB device!");
return false;
}
// clear calling identity so we can access the driver file.
long identity = Binder.clearCallingIdentity();
File file = new File("/dev/snd/midiC" + card + "D" + device);
try {
mInputStream = new FileInputStream(file);
mOutputStream = new FileOutputStream(file);
} catch (Exception e) {
Log.e(TAG, "could not open " + file);
return false;
} finally {
Binder.restoreCallingIdentity(identity);
}
return true;
}
void close() {
super.close();
try {
if (mInputStream != null) {
mInputStream.close();
}
if (mOutputStream != null) {
mOutputStream.close();
}
} catch (IOException e) {
}
}
// Reads a message from the ALSA driver.
// The driver may return multiple messages, so we have to read byte at a time.
int readMessage(byte[] buffer) throws IOException {
if (mInputStream.read(mBuffer, 0, 1) != 1) {
Log.e(TAG, "could not read command byte");
return -1;
}
int dataSize = MidiUtils.getMessageDataSize(mBuffer[0]);
if (dataSize < 0) {
return -1;
}
if (dataSize > 0) {
if (mInputStream.read(mBuffer, 1, dataSize) != dataSize) {
Log.e(TAG, "could not read command data");
return -1;
}
}
return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(),
0, // FIXME - multiple ports not supported yet
buffer);
}
// writes a message to the ALSA driver
void writeMessage(byte[] buffer, int count) throws IOException {
int offset = MidiDevice.getMessageOffset(buffer, count);
int size = MidiDevice.getMessageSize(buffer, count);
// FIXME - multiple ports not supported yet
mOutputStream.write(buffer, offset, count);
}
}

View File

@@ -1,91 +0,0 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server.midi;
import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// Our subclass of MidiDeviceBase to implement a virtual MIDI device
class VirtualMidiDevice extends MidiDeviceBase {
private static final String TAG = "VirtualMidiDevice";
private ParcelFileDescriptor[] mFileDescriptors;
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
public VirtualMidiDevice(MidiDeviceInfo info) {
super(info);
}
public boolean open() {
if (mInputStream != null && mOutputStream != null) {
// already open
return true;
}
try {
mFileDescriptors = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
FileDescriptor fd = mFileDescriptors[0].getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
return true;
} catch (IOException e) {
Log.e(TAG, "failed to create ParcelFileDescriptor pair");
return false;
}
}
void close() {
super.close();
try {
if (mInputStream != null) {
mInputStream.close();
}
if (mOutputStream != null) {
mOutputStream.close();
}
if (mFileDescriptors != null && mFileDescriptors[0] != null) {
mFileDescriptors[0].close();
// file descriptor 1 is passed to client process
}
} catch (IOException e) {
}
}
MidiDevice getProxy() {
return new MidiDevice(mDeviceInfo, mFileDescriptors[1]);
}
int readMessage(byte[] buffer) throws IOException {
int ret = mInputStream.read(buffer);
// for now, throw away the timestamp
return ret - 8;
}
void writeMessage(byte[] buffer, int count) throws IOException {
mOutputStream.write(buffer, 0, count);
}
}

View File

@@ -22,6 +22,7 @@ LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2010 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.
*/
#define LOG_TAG "UsbMidiDeviceJNI"
#define LOG_NDEBUG 0
#include "utils/Log.h"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
#include <stdio.h>
#include <errno.h>
#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sound/asound.h>
namespace android
{
static jclass sFileDescriptorClass;
static jint
android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
jint card, jint device)
{
char path[100];
snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
int fd = open(path, O_RDWR);
if (fd < 0) {
ALOGE("could not open %s", path);
return 0;
}
struct snd_rawmidi_info info;
memset(&info, 0, sizeof(info));
info.device = device;
int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
close(fd);
if (ret < 0) {
ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
return -1;
}
ALOGD("subdevices_count: %d", info.subdevices_count);
return info.subdevices_count;
}
static jobjectArray
android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
jint subdevice_count)
{
char path[100];
snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
if (!fds) {
return NULL;
}
// to support multiple subdevices we open the same file multiple times
for (int i = 0; i < subdevice_count; i++) {
int fd = open(path, O_RDWR);
if (fd < 0) {
ALOGE("open failed on %s for index %d", path, i);
return NULL;
}
jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
env->SetObjectArrayElement(fds, i, fileDescriptor);
env->DeleteLocalRef(fileDescriptor);
}
return fds;
}
static JNINativeMethod method_table[] = {
{ "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
{ "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
};
int register_android_server_UsbMidiDevice(JNIEnv *env)
{
jclass clazz = env->FindClass("java/io/FileDescriptor");
if (clazz == NULL) {
ALOGE("Can't find java/io/FileDescriptor");
return -1;
}
sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
if (clazz == NULL) {
ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
return -1;
}
return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
method_table, NELEM(method_table));
}
};

View File

@@ -32,6 +32,7 @@ int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -65,6 +66,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
register_android_server_UsbMidiDevice(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);

View File

@@ -74,7 +74,7 @@ import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.midi.MidiService;
import com.android.server.MidiService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;

View File

@@ -24,15 +24,15 @@ import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.media.AudioManager;
import android.midi.IMidiManager;
import android.os.FileObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -42,14 +42,13 @@ import java.util.ArrayList;
/**
* UsbAlsaManager manages USB audio and MIDI devices.
*/
public class UsbAlsaManager {
public final class UsbAlsaManager {
private static final String TAG = UsbAlsaManager.class.getSimpleName();
private static final boolean DEBUG = true;
private static final String ALSA_DIRECTORY = "/dev/snd/";
private final Context mContext;
private IMidiManager mMidiManager;
private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
@@ -60,6 +59,9 @@ public class UsbAlsaManager {
private final HashMap<UsbDevice,UsbAudioDevice>
mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
private final HashMap<UsbDevice,UsbMidiDevice>
mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
private final HashMap<String,AlsaDevice>
mAlsaDevices = new HashMap<String,AlsaDevice>();
@@ -121,8 +123,6 @@ public class UsbAlsaManager {
}
public void systemReady() {
final IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
mMidiManager = IMidiManager.Stub.asInterface(b);
mAlsaObserver.startWatching();
// add existing alsa devices
@@ -151,7 +151,6 @@ public class UsbAlsaManager {
intent.putExtra("device", audioDevice.mDevice);
intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
intent.putExtra("hasCapture", audioDevice.mHasCapture);
intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
intent.putExtra("class", audioDevice.mDeviceClass);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -235,9 +234,9 @@ public class UsbAlsaManager {
/*
* Select the default device of the specified card.
*/
/* package */ boolean selectCard(int card) {
/* package */ boolean selectAudioCard(int card) {
if (DEBUG) {
Slog.d(TAG, "selectCard() card:" + card);
Slog.d(TAG, "selectAudioCard() card:" + card);
}
if (!mCardsParser.isCardUsb(card)) {
// Don't. AudioPolicyManager has logic for falling back to internal devices.
@@ -259,7 +258,6 @@ public class UsbAlsaManager {
boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
boolean hasMidi = mDevicesParser.hasMIDIDevices(card);
int deviceClass =
(mCardsParser.isCardUsb(card)
? UsbAudioDevice.kAudioDeviceClass_External
@@ -275,21 +273,13 @@ public class UsbAlsaManager {
if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
return false;
}
//TODO - seems to me that we need to decouple the above tests for audio
// from the one below for MIDI.
// MIDI device file needed/present?
AlsaDevice midiDevice = null;
if (hasMidi) {
midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
}
if (DEBUG) {
Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
}
mSelectedAudioDevice =
new UsbAudioDevice(card, device, hasPlayback, hasCapture, hasMidi, deviceClass);
new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
mSelectedAudioDevice.mDeviceName = mCardsParser.getCardRecordFor(card).mCardName;
mSelectedAudioDevice.mDeviceDescription =
mCardsParser.getCardRecordFor(card).mCardDescription;
@@ -304,7 +294,7 @@ public class UsbAlsaManager {
Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
}
mCardsParser.scan();
return selectCard(mCardsParser.getDefaultCard());
return selectAudioCard(mCardsParser.getDefaultCard());
}
/* package */ void deviceAdded(UsbDevice usbDevice) {
@@ -314,7 +304,6 @@ public class UsbAlsaManager {
// Is there an audio interface in there?
boolean isAudioDevice = false;
AlsaDevice midiDevice = null;
// FIXME - handle multiple configurations?
int interfaceCount = usbDevice.getInterfaceCount();
@@ -347,36 +336,44 @@ public class UsbAlsaManager {
// If the default isn't a USB device, let the existing "select internal mechanism"
// handle the selection.
if (mCardsParser.isCardUsb(addedCard)) {
selectCard(addedCard);
selectAudioCard(addedCard);
mAudioDevices.put(usbDevice, mSelectedAudioDevice);
}
if (midiDevice != null && mMidiManager != null) {
try {
mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
} catch (RemoteException e) {
Slog.e(TAG, "MIDI Manager dead", e);
// look for MIDI devices
// Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
// Uncomment this next line if that behavior changes in the fugure.
// mDevicesParser.scan()
boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
if (hasMidi) {
int device = mDevicesParser.getDefaultDeviceNum(addedCard);
AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
if (alsaDevice != null) {
UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, usbDevice,
alsaDevice.mCard, alsaDevice.mDevice);
if (usbMidiDevice != null) {
mMidiDevices.put(usbDevice, usbMidiDevice);
}
}
}
}
}
/* package */ void deviceRemoved(UsbDevice device) {
/* package */ void deviceRemoved(UsbDevice usbDevice) {
if (DEBUG) {
Slog.d(TAG, "deviceRemoved(): " + device);
Slog.d(TAG, "deviceRemoved(): " + usbDevice);
}
UsbAudioDevice audioDevice = mAudioDevices.remove(device);
UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
if (audioDevice != null) {
if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
sendDeviceNotification(audioDevice, false);
}
if (audioDevice.mHasMIDI) {
try {
mMidiManager.alsaDeviceRemoved(device);
} catch (RemoteException e) {
Slog.e(TAG, "MIDI Manager dead", e);
}
}
}
UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
if (usbMidiDevice != null) {
IoUtils.closeQuietly(usbMidiDevice);
}
mSelectedAudioDevice = null;
@@ -400,10 +397,14 @@ public class UsbAlsaManager {
// Logging
//
public void dump(FileDescriptor fd, PrintWriter pw) {
pw.println(" USB AudioDevices:");
pw.println(" USB Audio Devices:");
for (UsbDevice device : mAudioDevices.keySet()) {
pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
}
pw.println(" USB MIDI Devices:");
for (UsbDevice device : mMidiDevices.keySet()) {
pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device));
}
}
public void logDevicesList(String title) {

View File

@@ -24,7 +24,6 @@ public final class UsbAudioDevice {
public int mDevice;
public boolean mHasPlayback;
public boolean mHasCapture;
public boolean mHasMIDI;
// Device "class" flags
public static final int kAudioDeviceClassMask = 0x00FFFFFF;
@@ -41,12 +40,11 @@ public final class UsbAudioDevice {
public String mDeviceDescription = "";
public UsbAudioDevice(int card, int device,
boolean hasPlayback, boolean hasCapture, boolean hasMidi, int deviceClass) {
boolean hasPlayback, boolean hasCapture, int deviceClass) {
mCard = card;
mDevice = device;
mHasPlayback = hasPlayback;
mHasCapture = hasCapture;
mHasMIDI = hasMidi;
mDeviceClass = deviceClass;
}
@@ -58,7 +56,6 @@ public final class UsbAudioDevice {
sb.append(", description: " + mDeviceDescription);
sb.append(", hasPlayback: " + mHasPlayback);
sb.append(", hasCapture: " + mHasCapture);
sb.append(", hasMidi: " + mHasMIDI);
sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]");
return sb.toString();
}

View File

@@ -0,0 +1,194 @@
/*
* 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 an
* limitations under the License.
*/
package com.android.server.usb;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.midi.MidiDeviceInfo;
import android.midi.MidiDeviceServer;
import android.midi.MidiManager;
import android.midi.MidiReceiver;
import android.midi.MidiSender;
import android.midi.MidiUtils;
import android.os.Bundle;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructPollfd;
import android.util.Log;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public final class UsbMidiDevice implements Closeable {
private static final String TAG = "UsbMidiDevice";
private final MidiDeviceServer mServer;
private final MidiReceiver[] mOutputPortReceivers;
// for polling multiple FileDescriptors for MIDI events
private final StructPollfd[] mPollFDs;
private final FileInputStream[] mInputStreams;
private final FileOutputStream[] mOutputStreams;
public static UsbMidiDevice create(Context context, UsbDevice usbDevice, int card, int device) {
MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
if (midiManager == null) {
Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
return null;
}
// FIXME - support devices with different number of input and output ports
int subDevices = nativeGetSubdeviceCount(card, device);
if (subDevices <= 0) {
Log.e(TAG, "nativeGetSubdeviceCount failed");
return null;
}
// FIXME - support devices with different number of input and output ports
FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
if (fileDescriptors == null) {
Log.e(TAG, "nativeOpen failed");
return null;
}
Bundle properties = new Bundle();
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, usbDevice.getManufacturerName());
properties.putString(MidiDeviceInfo.PROPERTY_MODEL, usbDevice.getProductName());
properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, usbDevice.getSerialNumber());
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
false, MidiDeviceInfo.TYPE_USB);
if (server == null) {
Log.e(TAG, "createDeviceServer failed");
return null;
}
return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
}
private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
FileDescriptor[] outputFiles) {
mServer = server;
int inputCount = inputFiles.length;
int outputCount = outputFiles.length;
mPollFDs = new StructPollfd[inputCount];
mInputStreams = new FileInputStream[inputCount];
for (int i = 0; i < inputCount; i++) {
FileDescriptor fd = inputFiles[i];
StructPollfd pollfd = new StructPollfd();
pollfd.fd = fd;
pollfd.events = (short)OsConstants.POLLIN;
mPollFDs[i] = pollfd;
mInputStreams[i] = new FileInputStream(fd);
}
mOutputStreams = new FileOutputStream[outputCount];
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
}
mOutputPortReceivers = new MidiReceiver[outputCount];
for (int port = 0; port < outputCount; port++) {
mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
}
for (int port = 0; port < inputCount; port++) {
final int portNumberF = port;
MidiReceiver receiver = new MidiReceiver() {
@Override
public void onPost(byte[] data, int offset, int count, long timestamp)
throws IOException {
// FIXME - timestamps are ignored, future posting not supported yet.
mOutputStreams[portNumberF].write(data, offset, count);
}
};
MidiSender sender = server.openInputPortSender(port);
sender.connect(receiver);
}
new Thread() {
@Override
public void run() {
byte[] buffer = new byte[3];
try {
while (true) {
// look for a readable FileDescriptor
for (int index = 0; index < mPollFDs.length; index++) {
StructPollfd pfd = mPollFDs[index];
if ((pfd.revents & OsConstants.POLLIN) != 0) {
// clear readable flag
pfd.revents = 0;
int count = readMessage(buffer, index);
mOutputPortReceivers[index].onPost(buffer, 0, count, System.nanoTime());
}
}
// poll if none are readable
Os.poll(mPollFDs, -1 /* infinite timeout */);
}
} catch (IOException e) {
Log.d(TAG, "reader thread exiting");
} catch (ErrnoException e) {
Log.d(TAG, "reader thread exiting");
}
}
}.start();
}
@Override
public void close() throws IOException {
mServer.close();
for (int i = 0; i < mInputStreams.length; i++) {
mInputStreams[i].close();
}
for (int i = 0; i < mOutputStreams.length; i++) {
mOutputStreams[i].close();
}
}
private int readMessage(byte[] buffer, int index) throws IOException {
FileInputStream inputStream = mInputStreams[index];
if (inputStream.read(buffer, 0, 1) != 1) {
Log.e(TAG, "could not read command byte");
return -1;
}
int dataSize = MidiUtils.getMessageDataSize(buffer[0]);
if (dataSize < 0) {
return -1;
}
if (dataSize > 0) {
if (inputStream.read(buffer, 1, dataSize) != dataSize) {
Log.e(TAG, "could not read command data");
return -1;
}
}
return dataSize + 1;
}
private static native int nativeGetSubdeviceCount(int card, int device);
private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
}