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:
Mike Lockwood
2014-12-01 13:54:59 -08:00
parent 0927c43c38
commit 67f8e8bd89
19 changed files with 1584 additions and 0 deletions

View File

@@ -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 \

View File

@@ -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) {

View File

@@ -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.

View 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);
}

View 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);
}

View 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;

View 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;
}
}

View 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;

View 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);
}
}

View 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");
}
}
}

View 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;
}

View 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);
}

View 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;
}
}
}

View 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;
}

View 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);
}
}
}
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}
}