Merge "MidiManager updates:"
This commit is contained in:
committed by
Android (Google) Code Review
commit
dea9dbe631
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) */];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
264
core/java/android/midi/MidiDeviceServer.java
Normal file
264
core/java/android/midi/MidiDeviceServer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
306
services/core/java/com/android/server/MidiService.java
Normal file
306
services/core/java/com/android/server/MidiService.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
121
services/core/jni/com_android_server_UsbMidiDevice.cpp
Normal file
121
services/core/jni/com_android_server_UsbMidiDevice.cpp
Normal 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));
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
194
services/usb/java/com/android/server/usb/UsbMidiDevice.java
Normal file
194
services/usb/java/com/android/server/usb/UsbMidiDevice.java
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user