MIDI Manager work in progress
Still to do: Add MidiInputPort and MidiOutputPort classes Schedule sending MIDI events in the future Security/permissions Reconsider interface for virtual devices Look into performance optimizations Change-Id: I9b7d63b196996a04be0a830efa913043da1328a8
This commit is contained in:
@@ -172,6 +172,8 @@ 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/IMidiListener.aidl \
|
||||
core/java/android/midi/IMidiManager.aidl \
|
||||
core/java/android/net/IConnectivityManager.aidl \
|
||||
core/java/android/net/IEthernetManager.aidl \
|
||||
core/java/android/net/IEthernetServiceListener.aidl \
|
||||
|
||||
@@ -80,6 +80,8 @@ import android.media.projection.MediaProjectionManager;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.tv.ITvInputManager;
|
||||
import android.media.tv.TvInputManager;
|
||||
import android.midi.IMidiManager;
|
||||
import android.midi.MidiManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.net.EthernetManager;
|
||||
@@ -777,6 +779,12 @@ class ContextImpl extends Context {
|
||||
IBinder b = ServiceManager.getService(APPWIDGET_SERVICE);
|
||||
return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
|
||||
}});
|
||||
|
||||
registerService(MIDI_SERVICE, new ServiceFetcher() {
|
||||
public Object createService(ContextImpl ctx) {
|
||||
IBinder b = ServiceManager.getService(MIDI_SERVICE);
|
||||
return new MidiManager(ctx, IMidiManager.Stub.asInterface(b));
|
||||
}});
|
||||
}
|
||||
|
||||
static ContextImpl getImpl(Context context) {
|
||||
|
||||
@@ -2142,6 +2142,7 @@ public abstract class Context {
|
||||
MEDIA_SESSION_SERVICE,
|
||||
BATTERY_SERVICE,
|
||||
JOB_SCHEDULER_SERVICE,
|
||||
MIDI_SERVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ServiceName {}
|
||||
@@ -2914,6 +2915,15 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService} to retrieve a
|
||||
* {@link android.midi.MidiManager} for accessing the MIDI service.
|
||||
*
|
||||
* @see #getSystemService
|
||||
* @hide
|
||||
*/
|
||||
public static final String MIDI_SERVICE = "midi";
|
||||
|
||||
/**
|
||||
* Determine whether the given permission is allowed for a particular
|
||||
* process and user ID running in the system.
|
||||
|
||||
26
core/java/android/midi/IMidiListener.aidl
Normal file
26
core/java/android/midi/IMidiListener.aidl
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.midi.MidiDeviceInfo;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IMidiListener
|
||||
{
|
||||
void onDeviceAdded(in MidiDeviceInfo device);
|
||||
void onDeviceRemoved(in MidiDeviceInfo device);
|
||||
}
|
||||
46
core/java/android/midi/IMidiManager.aidl
Normal file
46
core/java/android/midi/IMidiManager.aidl
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.hardware.usb.UsbDevice;
|
||||
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
|
||||
{
|
||||
MidiDeviceInfo[] getDeviceList();
|
||||
|
||||
// for device creation & removal notifications
|
||||
void registerListener(IBinder token, in IMidiListener listener);
|
||||
void unregisterListener(IBinder token, in IMidiListener listener);
|
||||
|
||||
// for communicating with MIDI devices
|
||||
ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device);
|
||||
|
||||
// for implementing virtual MIDI devices
|
||||
MidiDevice registerVirtualDevice(IBinder token, 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);
|
||||
}
|
||||
19
core/java/android/midi/MidiDevice.aidl
Normal file
19
core/java/android/midi/MidiDevice.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable MidiDevice;
|
||||
292
core/java/android/midi/MidiDevice.java
Normal file
292
core/java/android/midi/MidiDevice.java
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
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 {
|
||||
private static final String TAG = "MidiDevice";
|
||||
|
||||
private final MidiDeviceInfo mDeviceInfo;
|
||||
private ParcelFileDescriptor mParcelFileDescriptor;
|
||||
private FileInputStream mInputStream;
|
||||
private FileOutputStream mOutputStream;
|
||||
private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
|
||||
|
||||
/**
|
||||
* Minimum size of packed message as sent through our ParcelFileDescriptor
|
||||
* 8 bytes for timestamp and 1 to 3 bytes for message
|
||||
* @hide
|
||||
*/
|
||||
public static final int MIN_PACKED_MESSAGE_SIZE = 9;
|
||||
|
||||
/**
|
||||
* Maximum size of packed message as sent through our ParcelFileDescriptor
|
||||
* 8 bytes for timestamp and 1 to 3 bytes for message
|
||||
* @hide
|
||||
*/
|
||||
public static final int MAX_PACKED_MESSAGE_SIZE = 11;
|
||||
|
||||
// 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 {
|
||||
mReceivers.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) {
|
||||
mReceivers.remove(receiver);
|
||||
}
|
||||
deadReceivers.clear();
|
||||
}
|
||||
// exit if we have no receivers left
|
||||
if (mReceivers.size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "read failed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is the receiver that clients use for sending events to this device.
|
||||
private final MidiReceiver mReceiver = new MidiReceiver() {
|
||||
private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE];
|
||||
public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
|
||||
synchronized (mBuffer) {
|
||||
int length = packMessage(msg, offset, count, timestamp, mBuffer);
|
||||
mOutputStream.write(mBuffer, 0, length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Our MidiSender object, to which clients can attach MidiReceivers.
|
||||
private final MidiSender mSender = new MidiSender() {
|
||||
public void connect(MidiReceiver receiver) {
|
||||
synchronized (mReceivers) {
|
||||
if (mReceivers.size() == 0) {
|
||||
mThread.start();
|
||||
}
|
||||
mReceivers.add(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(MidiReceiver receiver) {
|
||||
synchronized (mReceivers) {
|
||||
mReceivers.remove(receiver);
|
||||
if (mReceivers.size() == 0) {
|
||||
// ???
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MidiDevice should only be instantiated by MidiManager or MidiService
|
||||
* @hide
|
||||
*/
|
||||
public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
|
||||
mDeviceInfo = deviceInfo;
|
||||
mParcelFileDescriptor = pfd;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
void close() {
|
||||
try {
|
||||
if (mInputStream != null) {
|
||||
mInputStream.close();
|
||||
}
|
||||
if (mOutputStream != null) {
|
||||
mOutputStream.close();
|
||||
}
|
||||
mParcelFileDescriptor.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// returns our MidiDeviceInfo object, which describes this device
|
||||
public MidiDeviceInfo getInfo() {
|
||||
return mDeviceInfo;
|
||||
}
|
||||
|
||||
// returns our MidiReceiver, which clients can use for sending events to this device.
|
||||
public MidiReceiver getReceiver() {
|
||||
return mReceiver;
|
||||
}
|
||||
|
||||
// Returns our MidiSender object, to which clients can attach MidiReceivers.
|
||||
public MidiSender getSender() {
|
||||
return mSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public MidiDevice[] newArray(int size) {
|
||||
return new MidiDevice[size];
|
||||
}
|
||||
};
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
*
|
||||
* @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
|
||||
return bufferLength - 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
19
core/java/android/midi/MidiDeviceInfo.aidl
Normal file
19
core/java/android/midi/MidiDeviceInfo.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable MidiDeviceInfo;
|
||||
193
core/java/android/midi/MidiDeviceInfo.java
Normal file
193
core/java/android/midi/MidiDeviceInfo.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* This class contains information to describe a MIDI device.
|
||||
* For now we only have information that can be retrieved easily for USB devices,
|
||||
* but we will probably expand this in the future.
|
||||
*
|
||||
* This class is just an immutable object to encapsulate the MIDI device description.
|
||||
* Use the MidiDevice class to actually communicate with devices.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class MidiDeviceInfo implements Parcelable {
|
||||
|
||||
private static final String TAG = "MidiDeviceInfo";
|
||||
|
||||
public static final int TYPE_USB = 1;
|
||||
public static final int TYPE_VIRTUAL = 2;
|
||||
|
||||
private final int mType; // USB or virtual
|
||||
private final int mId; // unique ID generated by MidiService
|
||||
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}.
|
||||
* Matches the USB device manufacturer name string for USB MIDI devices.
|
||||
*/
|
||||
public static final String PROPERTY_MANUFACTURER = "manufacturer";
|
||||
|
||||
/**
|
||||
* Bundle key for the device's model name property.
|
||||
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
|
||||
* Matches the USB device product name string for USB MIDI devices.
|
||||
*/
|
||||
public static final String PROPERTY_MODEL = "model";
|
||||
|
||||
/**
|
||||
* Bundle key for the device's serial number property.
|
||||
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
|
||||
* Matches the USB device serial number for USB MIDI devices.
|
||||
*/
|
||||
public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
|
||||
|
||||
/**
|
||||
* Bundle key for the device's {@link android.hardware.usb.UsbDevice}.
|
||||
* Only set for USB MIDI devices.
|
||||
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
|
||||
*/
|
||||
public static final String PROPERTY_USB_DEVICE = "usb_device";
|
||||
|
||||
/**
|
||||
* MidiDeviceInfo should only be instantiated by MidiService implementation
|
||||
* @hide
|
||||
*/
|
||||
public MidiDeviceInfo(int type, int id, Bundle properties) {
|
||||
mType = type;
|
||||
mId = id;
|
||||
mProperties = properties;
|
||||
mAlsaCard = -1;
|
||||
mAlsaDevice = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* MidiDeviceInfo should only be instantiated by MidiService implementation
|
||||
* @hide
|
||||
*/
|
||||
public MidiDeviceInfo(int type, int id, Bundle properties,
|
||||
int alsaCard, int alsaDevice) {
|
||||
mType = type;
|
||||
mId = id;
|
||||
mProperties = properties;
|
||||
mAlsaCard = alsaCard;
|
||||
mAlsaDevice = alsaDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the device.
|
||||
*
|
||||
* @return the device's type
|
||||
*/
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the device.
|
||||
* This ID is generated by the MIDI service and is not persistent across device unplugs.
|
||||
*
|
||||
* @return the device's ID
|
||||
*/
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link android.os.Bundle} containing the device's properties.
|
||||
*
|
||||
* @return the device's properties
|
||||
*/
|
||||
public Bundle getProperties() {
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int getAlsaCard() {
|
||||
return mAlsaCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int getAlsaDevice() {
|
||||
return mAlsaDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof MidiDeviceInfo) {
|
||||
return (((MidiDeviceInfo)o).mId == mId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ("MidiDeviceInfo[mType=" + mType +
|
||||
",mId=" + mId +
|
||||
",mProperties=" + mProperties +
|
||||
",mAlsaCard=" + mAlsaCard +
|
||||
",mAlsaDevice=" + mAlsaDevice);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
|
||||
new Parcelable.Creator<MidiDeviceInfo>() {
|
||||
public MidiDeviceInfo createFromParcel(Parcel in) {
|
||||
int type = in.readInt();
|
||||
int id = in.readInt();
|
||||
Bundle properties = in.readBundle();
|
||||
int card = in.readInt();
|
||||
int device = in.readInt();
|
||||
return new MidiDeviceInfo(type, id, properties, card, device);
|
||||
}
|
||||
|
||||
public MidiDeviceInfo[] newArray(int size) {
|
||||
return new MidiDeviceInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeInt(mType);
|
||||
parcel.writeInt(mId);
|
||||
parcel.writeBundle(mProperties);
|
||||
parcel.writeInt(mAlsaCard);
|
||||
parcel.writeInt(mAlsaDevice);
|
||||
}
|
||||
}
|
||||
156
core/java/android/midi/MidiManager.java
Normal file
156
core/java/android/midi/MidiManager.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class is the public application interface to the MIDI service.
|
||||
*
|
||||
* <p>You can obtain an instance of this class by calling
|
||||
* {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
|
||||
*
|
||||
* {@samplecode
|
||||
* MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);}
|
||||
* @hide
|
||||
*/
|
||||
public class MidiManager {
|
||||
private static final String TAG = "MidiManager";
|
||||
|
||||
private final Context mContext;
|
||||
private final IMidiManager mService;
|
||||
private final IBinder mToken = new Binder();
|
||||
|
||||
private HashMap<DeviceCallback,DeviceListener> mDeviceListeners =
|
||||
new HashMap<DeviceCallback,DeviceListener>();
|
||||
|
||||
// Binder stub for receiving device notifications from MidiService
|
||||
private class DeviceListener extends IMidiListener.Stub {
|
||||
private DeviceCallback mCallback;
|
||||
|
||||
public DeviceListener(DeviceCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void onDeviceAdded(MidiDeviceInfo device) {
|
||||
mCallback.onDeviceAdded(device);
|
||||
}
|
||||
|
||||
public void onDeviceRemoved(MidiDeviceInfo device) {
|
||||
mCallback.onDeviceRemoved(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback interface clients to receive Device added and removed notifications
|
||||
public interface DeviceCallback {
|
||||
void onDeviceAdded(MidiDeviceInfo device);
|
||||
void onDeviceRemoved(MidiDeviceInfo device);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public MidiManager(Context context, IMidiManager service) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
// Used by clients to register for Device added and removed notifications
|
||||
public void registerDeviceCallback(DeviceCallback callback) {
|
||||
DeviceListener deviceListener = new DeviceListener(callback);
|
||||
try {
|
||||
mService.registerListener(mToken, deviceListener);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in registerDeviceListener");
|
||||
return;
|
||||
}
|
||||
mDeviceListeners.put(callback, deviceListener);
|
||||
}
|
||||
|
||||
// Used by clients to unregister for device added and removed notifications
|
||||
public void unregisterDeviceCallback(DeviceCallback callback) {
|
||||
DeviceListener deviceListener = mDeviceListeners.remove(callback);
|
||||
if (deviceListener != null) {
|
||||
try {
|
||||
mService.unregisterListener(mToken, deviceListener);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in unregisterDeviceListener");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MidiDeviceInfo[] getDeviceList() {
|
||||
try {
|
||||
return mService.getDeviceList();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in getDeviceList");
|
||||
return new MidiDeviceInfo[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Use this if you want to communicate with a MIDI device.
|
||||
public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
|
||||
try {
|
||||
ParcelFileDescriptor pfd = mService.openDevice(mToken, deviceInfo);
|
||||
if (pfd == 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;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in openDevice");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use this if you want to register and implement a virtual device.
|
||||
// The MidiDevice returned by this method is the proxy you use to implement the device.
|
||||
public MidiDevice createVirtualDevice(Bundle properties) {
|
||||
try {
|
||||
MidiDevice device = mService.registerVirtualDevice(mToken, properties);
|
||||
if (device != null && !device.open()) {
|
||||
device = null;
|
||||
}
|
||||
return device;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in createVirtualDevice");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void closeVirtualDevice(MidiDevice device) {
|
||||
try {
|
||||
device.close();
|
||||
mService.unregisterVirtualDevice(mToken, device.getInfo());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in unregisterVirtualDevice");
|
||||
}
|
||||
}
|
||||
}
|
||||
30
core/java/android/midi/MidiReceiver.java
Normal file
30
core/java/android/midi/MidiReceiver.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
/**
|
||||
* Interface for receiving events from a MIDI device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface MidiReceiver {
|
||||
// NOTE: the msg array is only valid within the context of this call.
|
||||
// the byte array may get reused by the MIDI device for the next message.
|
||||
public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException;
|
||||
}
|
||||
28
core/java/android/midi/MidiSender.java
Normal file
28
core/java/android/midi/MidiSender.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Interface provided by a device to allow attaching
|
||||
* MidiReceivers to a MIDI device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface MidiSender {
|
||||
public void connect(MidiReceiver receiver);
|
||||
public void disconnect(MidiReceiver receiver);
|
||||
}
|
||||
63
core/java/android/midi/MidiUtils.java
Normal file
63
core/java/android/midi/MidiUtils.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.util.Log;
|
||||
|
||||
/**
|
||||
* Class containing miscellaneous MIDI utilities.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class MidiUtils {
|
||||
private static final String TAG = "MidiUtils";
|
||||
|
||||
/**
|
||||
* Returns data size of a MIDI message based on the message's command byte
|
||||
* @param b the message command byte
|
||||
* @return the message's data length
|
||||
*/
|
||||
public static int getMessageDataSize(byte b) {
|
||||
switch (b & 0xF0) {
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
case 0xA0:
|
||||
case 0xB0:
|
||||
case 0xE0:
|
||||
return 2;
|
||||
case 0xC0:
|
||||
case 0xD0:
|
||||
return 1;
|
||||
case 0xF0:
|
||||
switch (b & 0x0F) {
|
||||
case 0x00:
|
||||
Log.e(TAG, "System Exclusive not supported yet");
|
||||
return -1;
|
||||
case 0x01:
|
||||
case 0x03:
|
||||
return 1;
|
||||
case 0x02:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "unknown MIDI command " + b);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
170
services/core/java/com/android/server/midi/MidiDeviceBase.java
Normal file
170
services/core/java/com/android/server/midi/MidiDeviceBase.java
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
277
services/core/java/com/android/server/midi/MidiService.java
Normal file
277
services/core/java/com/android/server/midi/MidiService.java
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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, 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,
|
||||
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);
|
||||
|
||||
deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
services/core/java/com/android/server/midi/UsbMidiDevice.java
Normal file
113
services/core/java/com/android/server/midi/UsbMidiDevice.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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(), 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);
|
||||
mOutputStream.write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +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.net.NetworkPolicyManagerService;
|
||||
import com.android.server.net.NetworkStatsService;
|
||||
import com.android.server.notification.NotificationManagerService;
|
||||
@@ -410,6 +411,7 @@ public final class SystemServer {
|
||||
AudioService audioService = null;
|
||||
MmsServiceBroker mmsService = null;
|
||||
EntropyMixer entropyMixer = null;
|
||||
MidiService midiService = null;
|
||||
|
||||
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
|
||||
boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
|
||||
@@ -521,6 +523,7 @@ public final class SystemServer {
|
||||
LockSettingsService lockSettings = null;
|
||||
AssetAtlasService atlas = null;
|
||||
MediaRouterService mediaRouter = null;
|
||||
MidiService midi = null;
|
||||
|
||||
// Bring up services needed for UI.
|
||||
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
|
||||
@@ -827,6 +830,16 @@ public final class SystemServer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!disableNonCoreServices) {
|
||||
try {
|
||||
Slog.i(TAG, "MIDI Service");
|
||||
ServiceManager.addService(Context.MIDI_SERVICE,
|
||||
new MidiService(context));
|
||||
} catch (Throwable e) {
|
||||
reportWtf("starting MIDI Service", e);
|
||||
}
|
||||
}
|
||||
|
||||
mSystemServiceManager.startService(TwilightService.class);
|
||||
|
||||
mSystemServiceManager.startService(UiModeManagerService.class);
|
||||
|
||||
@@ -24,7 +24,11 @@ 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;
|
||||
@@ -44,6 +48,7 @@ public class UsbAudioManager {
|
||||
private static final String ALSA_DIRECTORY = "/dev/snd/";
|
||||
|
||||
private final Context mContext;
|
||||
private IMidiManager mMidiManager;
|
||||
|
||||
private final class AudioDevice {
|
||||
public int mCard;
|
||||
@@ -132,6 +137,8 @@ public class UsbAudioManager {
|
||||
}
|
||||
|
||||
public void systemReady() {
|
||||
final IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
|
||||
mMidiManager = IMidiManager.Stub.asInterface(b);
|
||||
mAlsaObserver.startWatching();
|
||||
|
||||
// add existing alsa devices
|
||||
@@ -241,6 +248,7 @@ public class UsbAudioManager {
|
||||
|
||||
// Is there an audio interface in there?
|
||||
boolean isAudioDevice = false;
|
||||
AlsaDevice midiDevice = null;
|
||||
|
||||
// FIXME - handle multiple configurations?
|
||||
int interfaceCount = usbDevice.getInterfaceCount();
|
||||
@@ -289,6 +297,11 @@ public class UsbAudioManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// MIDI device file needed/present?
|
||||
if (hasMidi) {
|
||||
midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG,
|
||||
"usb: hasPlayback:" + hasPlayback +
|
||||
@@ -299,6 +312,14 @@ public class UsbAudioManager {
|
||||
AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
|
||||
mAudioDevices.put(usbDevice, audioDevice);
|
||||
sendDeviceNotification(audioDevice, true);
|
||||
|
||||
if (midiDevice != null && mMidiManager != null) {
|
||||
try {
|
||||
mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "MIDI Manager dead", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void deviceRemoved(UsbDevice device) {
|
||||
@@ -311,6 +332,13 @@ public class UsbAudioManager {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user