diff options
author | Mike Lockwood <lockwood@google.com> | 2015-02-20 08:28:47 -0800 |
---|---|---|
committer | Mike Lockwood <lockwood@google.com> | 2015-02-23 14:32:23 -0800 |
commit | b673770f7172d4fca9bc05de1f36bc53e93eb247 (patch) | |
tree | 5d58c97188d3d7c4f9d7faf5376e586374a17624 /media | |
parent | c3c2ff70a23ff31bec72e7794ea33552ca106438 (diff) | |
download | frameworks_base-b673770f7172d4fca9bc05de1f36bc53e93eb247.zip frameworks_base-b673770f7172d4fca9bc05de1f36bc53e93eb247.tar.gz frameworks_base-b673770f7172d4fca9bc05de1f36bc53e93eb247.tar.bz2 |
Add MIDI Manager API tweaks:
- Change the package name from android.midi to android.media.midi
- Add option for specifying a Handler for DeviceCallback notifications
Change-Id: Ia9e9817a651c06299f4e02ee1da3c9666ff64cb9
Diffstat (limited to 'media')
-rw-r--r-- | media/java/android/media/midi/IMidiDeviceServer.aidl | 26 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiListener.aidl | 26 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiManager.aidl | 41 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDevice.java | 102 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceInfo.aidl | 19 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceInfo.java | 204 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceServer.java | 268 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiInputPort.java | 80 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiManager.java | 222 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiOutputPort.java | 135 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiPort.java | 148 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiReceiver.java | 44 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiSender.java | 40 |
13 files changed, 1355 insertions, 0 deletions
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl new file mode 100644 index 0000000..71914ad --- /dev/null +++ b/media/java/android/media/midi/IMidiDeviceServer.aidl @@ -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.media.midi; + +import android.os.ParcelFileDescriptor; + +/** @hide */ +interface IMidiDeviceServer +{ + ParcelFileDescriptor openInputPort(int portNumber); + ParcelFileDescriptor openOutputPort(int portNumber); +} diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiListener.aidl new file mode 100644 index 0000000..a4129e9 --- /dev/null +++ b/media/java/android/media/midi/IMidiListener.aidl @@ -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.media.midi; + +import android.media.midi.MidiDeviceInfo; + +/** @hide */ +oneway interface IMidiListener +{ + void onDeviceAdded(in MidiDeviceInfo device); + void onDeviceRemoved(in MidiDeviceInfo device); +} diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl new file mode 100644 index 0000000..bba35f5 --- /dev/null +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.media.midi.IMidiDeviceServer; +import android.media.midi.IMidiListener; +import android.media.midi.MidiDeviceInfo; +import android.os.Bundle; +import android.os.IBinder; + +/** @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 + IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); + + // for implementing virtual MIDI devices + MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, + int numOutputPorts, in Bundle properties, boolean isPrivate, int type); + void unregisterDeviceServer(in IMidiDeviceServer server); +} diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java new file mode 100644 index 0000000..36710fd --- /dev/null +++ b/media/java/android/media/midi/MidiDevice.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +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}. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public final class MidiDevice { + private static final String TAG = "MidiDevice"; + + private final MidiDeviceInfo mDeviceInfo; + private final IMidiDeviceServer mServer; + + /** + * MidiDevice should only be instantiated by MidiManager + * @hide + */ + public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) { + mDeviceInfo = deviceInfo; + mServer = server; + } + + /** + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object + */ + public MidiDeviceInfo getInfo() { + return mDeviceInfo; + } + + /** + * Called to open a {@link MidiInputPort} for the specified port number. + * + * @param portNumber the number of the input port to open + * @return the {@link MidiInputPort} + */ + public MidiInputPort openInputPort(int portNumber) { + try { + ParcelFileDescriptor pfd = mServer.openInputPort(portNumber); + if (pfd == null) { + return null; + } + return new MidiInputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openInputPort"); + return null; + } + } + + /** + * Called to open a {@link MidiOutputPort} for the specified port number. + * + * @param portNumber the number of the output port to open + * @return the {@link MidiOutputPort} + */ + public MidiOutputPort openOutputPort(int portNumber) { + try { + ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber); + if (pfd == null) { + return null; + } + return new MidiOutputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openOutputPort"); + return null; + } + } + + @Override + public String toString() { + return ("MidiDevice: " + mDeviceInfo.toString()); + } +} diff --git a/media/java/android/media/midi/MidiDeviceInfo.aidl b/media/java/android/media/midi/MidiDeviceInfo.aidl new file mode 100644 index 0000000..f2f37a2 --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceInfo.aidl @@ -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.media.midi; + +parcelable MidiDeviceInfo; diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java new file mode 100644 index 0000000..fd35052 --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.os.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. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiDeviceInfo implements Parcelable { + + private static final String TAG = "MidiDeviceInfo"; + + /** + * Constant representing USB MIDI devices for {@link #getType} + */ + public static final int TYPE_USB = 1; + + /** + * Constant representing virtual (software based) MIDI devices for {@link #getType} + */ + 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 int mInputPortCount; + private final int mOutputPortCount; + private final Bundle mProperties; + + /** + * 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"; + + /** + * Bundle key for the device's ALSA card number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_ALSA_CARD = "alsa_card"; + + /** + * Bundle key for the device's ALSA device number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_ALSA_DEVICE = "alsa_device"; + + /** + * MidiDeviceInfo should only be instantiated by MidiService implementation + * @hide + */ + public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, + Bundle properties) { + mType = type; + mId = id; + mInputPortCount = numInputPorts; + mOutputPortCount = numOutputPorts; + mProperties = properties; + } + + /** + * 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 device's number of input ports. + * + * @return the number of input ports + */ + public int getInputPortCount() { + return mInputPortCount; + } + + /** + * Returns the device's number of output ports. + * + * @return the number of output ports + */ + public int getOutputPortCount() { + return mOutputPortCount; + } + + /** + * Returns the {@link android.os.Bundle} containing the device's properties. + * + * @return the device's properties + */ + public Bundle getProperties() { + return mProperties; + } + + @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 + + ",mInputPortCount=" + mInputPortCount + + ",mOutputPortCount=" + mOutputPortCount + + ",mProperties=" + mProperties); + } + + public static final Parcelable.Creator<MidiDeviceInfo> CREATOR = + new Parcelable.Creator<MidiDeviceInfo>() { + public MidiDeviceInfo createFromParcel(Parcel in) { + int type = in.readInt(); + int id = in.readInt(); + int inputPorts = in.readInt(); + int outputPorts = in.readInt(); + Bundle properties = in.readBundle(); + return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties); + } + + 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.writeInt(mInputPortCount); + parcel.writeInt(mOutputPortCount); + parcel.writeBundle(mProperties); + } +} diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java new file mode 100644 index 0000000..3317baa --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.system.OsConstants; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used to provide the implemention of MIDI device. + * Applications may call {@link MidiManager#createDeviceServer} + * to create an instance of this class to implement a virtual MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public final class MidiDeviceServer implements Closeable { + private static final String TAG = "MidiDeviceServer"; + + private final IMidiManager mMidiManager; + + // MidiDeviceInfo for the device implemented by this server + private MidiDeviceInfo mDeviceInfo; + private int mInputPortCount; + private int mOutputPortCount; + + // output ports for receiving messages from our clients + // we can have only one per port number + private MidiOutputPort[] mInputPortSenders; + + // receivers attached to our input ports + private ArrayList<MidiReceiver>[] mInputPortReceivers; + + // input ports for sending messages to our clients + // we can have multiple outputs per port number + private ArrayList<MidiInputPort>[] mOutputPortReceivers; + + // subclass of MidiInputPort for passing to clients + // that notifies us when the connection has failed + private class ServerInputPort extends MidiInputPort { + ServerInputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mOutputPortReceivers) { + mOutputPortReceivers[getPortNumber()].clear(); + } + } + } + + // subclass of MidiOutputPort for passing to clients + // that notifies us when the connection has failed + private class ServerOutputPort extends MidiOutputPort { + ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mInputPortSenders) { + mInputPortSenders[getPortNumber()] = null; + } + } + } + + // Binder interface stub for receiving connection requests from clients + private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { + + @Override + public ParcelFileDescriptor openInputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mInputPortCount) { + Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber); + return null; + } + + ParcelFileDescriptor result = null; + + synchronized (mInputPortSenders) { + if (mInputPortSenders[portNumber] != null) { + Log.d(TAG, "port " + portNumber + " already open"); + return null; + } + + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber); + mInputPortSenders[portNumber] = newOutputPort; + result = pair[1]; + + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + newOutputPort.connect(receivers.get(i)); + } + } + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); + return null; + } + } + + return result; + } + + @Override + public ParcelFileDescriptor openOutputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mOutputPortCount) { + Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber); + return null; + } + synchronized (mOutputPortReceivers) { + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber)); + return pair[1]; + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); + return null; + } + } + } + }; + + /* package */ MidiDeviceServer(IMidiManager midiManager) { + mMidiManager = midiManager; + } + + /* package */ IMidiDeviceServer getBinderInterface() { + return mServer; + } + + /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { + if (mDeviceInfo != null) { + throw new IllegalStateException("setDeviceInfo should only be called once"); + } + mDeviceInfo = deviceInfo; + mInputPortCount = deviceInfo.getInputPortCount(); + mOutputPortCount = deviceInfo.getOutputPortCount(); + mInputPortSenders = new MidiOutputPort[mInputPortCount]; + + mInputPortReceivers = new ArrayList[mInputPortCount]; + for (int i = 0; i < mInputPortCount; i++) { + mInputPortReceivers[i] = new ArrayList<MidiReceiver>(); + } + + mOutputPortReceivers = new ArrayList[mOutputPortCount]; + for (int i = 0; i < mOutputPortCount; i++) { + mOutputPortReceivers[i] = new ArrayList<MidiInputPort>(); + } + } + + @Override + public void close() throws IOException { + try { + // FIXME - close input and output ports too? + mMidiManager.unregisterDeviceServer(mServer); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterDeviceServer"); + } + } + + /** + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object + */ + public MidiDeviceInfo getInfo() { + return mDeviceInfo; + } + + /** + * Called to open a {@link MidiSender} to allow receiving MIDI messages + * on the device's input port for the specified port number. + * + * @param portNumber the number of the input port + * @return the {@link MidiSender} + */ + public MidiSender openInputPortSender(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiSender() { + + @Override + public void connect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.add(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.connect(receiver); + } + } + } + } + + @Override + public void disconnect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.remove(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.disconnect(receiver); + } + } + } + } + }; + } + + /** + * Called to open a {@link MidiReceiver} to allow sending MIDI messages + * on the virtual device's output port for the specified port number. + * + * @param portNumber the number of the output port + * @return the {@link MidiReceiver} + */ + public MidiReceiver openOutputPortReceiver(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiReceiver() { + + @Override + public void post(byte[] msg, int offset, int count, long timestamp) throws IOException { + ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + // FIXME catch errors and remove dead ones + receivers.get(i).post(msg, offset, count, timestamp); + } + } + } + }; + } +} diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java new file mode 100644 index 0000000..730d364 --- /dev/null +++ b/media/java/android/media/midi/MidiInputPort.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.os.ParcelFileDescriptor; + +import libcore.io.IoUtils; + +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * This class is used for sending data to a port on a MIDI device + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiInputPort extends MidiPort implements MidiReceiver { + + private final FileOutputStream mOutputStream; + + // buffer to use for sending messages out our output stream + private final byte[] mBuffer = new byte[MAX_PACKET_SIZE]; + + /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) { + super(portNumber); + mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd); + } + + /** + * Writes a MIDI message to the input port + * + * @param msg byte array containing the message + * @param offset offset of first byte of the message in msg byte array + * @param count size of the message in bytes + * @param timestamp future time to post the message (based on + * {@link java.lang.System#nanoTime} + */ + public void post(byte[] msg, int offset, int count, long timestamp) throws IOException { + assert(offset >= 0 && count >= 0 && offset + count <= msg.length); + + synchronized (mBuffer) { + try { + while (count > 0) { + int length = packMessage(msg, offset, count, timestamp, mBuffer); + mOutputStream.write(mBuffer, 0, length); + int sent = getMessageSize(mBuffer, length); + assert(sent >= 0 && sent <= length); + + offset += sent; + count -= sent; + } + } catch (IOException e) { + IoUtils.closeQuietly(mOutputStream); + // report I/O failure + onIOException(); + throw e; + } + } + } + + @Override + public void close() throws IOException { + mOutputStream.close(); + } +} diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java new file mode 100644 index 0000000..410120d --- /dev/null +++ b/media/java/android/media/midi/MidiManager.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Bundle; +import android.os.Handler; +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);} + * + * CANDIDATE FOR PUBLIC API + * @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 final DeviceCallback mCallback; + private final Handler mHandler; + + public DeviceListener(DeviceCallback callback, Handler handler) { + mCallback = callback; + mHandler = handler; + } + + public void onDeviceAdded(MidiDeviceInfo device) { + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceAdded(deviceF); + } + }); + } else { + mCallback.onDeviceAdded(device); + } + } + + public void onDeviceRemoved(MidiDeviceInfo device) { + if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceRemoved(deviceF); + } + }); + } else { + mCallback.onDeviceRemoved(device); + } + } + } + + /** + * Callback class used for clients to receive MIDI device added and removed notifications + */ + public static class DeviceCallback { + /** + * Called to notify when a new MIDI device has been added + * + * @param device a {@link MidiDeviceInfo} for the newly added device + */ + public void onDeviceAdded(MidiDeviceInfo device) { + } + + /** + * Called to notify when a MIDI device has been removed + * + * @param device a {@link MidiDeviceInfo} for the removed device + */ + public void onDeviceRemoved(MidiDeviceInfo device) { + } + } + + /** + * @hide + */ + public MidiManager(Context context, IMidiManager service) { + mContext = context; + mService = service; + } + + /** + * Registers a callback to receive notifications when MIDI devices are added and removed. + * + * @param callback a {@link DeviceCallback} for MIDI device notifications + * @param handler The {@link android.os.Handler Handler} that will be used for delivering the + * device notifications. If handler is null, then the thread used for the + * callback is unspecified. + */ + public void registerDeviceCallback(DeviceCallback callback, Handler handler) { + DeviceListener deviceListener = new DeviceListener(callback, handler); + try { + mService.registerListener(mToken, deviceListener); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in registerDeviceListener"); + return; + } + mDeviceListeners.put(callback, deviceListener); + } + + /** + * Unregisters a {@link DeviceCallback}. + * + * @param callback a {@link DeviceCallback} to unregister + */ + 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"); + } + } + } + + /** + * Gets the list of all connected MIDI devices. + * + * @return an array of all MIDI devices + */ + public MidiDeviceInfo[] getDeviceList() { + try { + return mService.getDeviceList(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getDeviceList"); + return new MidiDeviceInfo[0]; + } + } + + /** + * Opens a MIDI device for reading and writing. + * + * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open + * @return a {@link MidiDevice} object for the device + */ + public MidiDevice openDevice(MidiDeviceInfo deviceInfo) { + try { + IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); + if (server == null) { + Log.e(TAG, "could not open device " + deviceInfo); + return null; + } + return new MidiDevice(deviceInfo, server); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openDevice"); + } + return null; + } + + /** @hide */ + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate, int type) { + try { + MidiDeviceServer server = new MidiDeviceServer(mService); + MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), + numInputPorts, numOutputPorts, properties, isPrivate, type); + if (deviceInfo == null) { + Log.e(TAG, "registerVirtualDevice failed"); + return null; + } + server.setDeviceInfo(deviceInfo); + return server; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in createVirtualDevice"); + return null; + } + } + + /** + * Creates a new MIDI virtual device. + * + * @param numInputPorts number of input ports for the virtual device + * @param numOutputPorts number of output ports for the virtual device + * @param properties a {@link android.os.Bundle} containing properties describing the device + * @param isPrivate true if this device should only be visible and accessible to apps + * with the same UID as the caller + * @return a {@link MidiDeviceServer} object to locally represent the device + */ + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate) { + return createDeviceServer(numInputPorts, numOutputPorts, properties, + isPrivate, MidiDeviceInfo.TYPE_VIRTUAL); + } + +} diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java new file mode 100644 index 0000000..83ddeeb --- /dev/null +++ b/media/java/android/media/midi/MidiOutputPort.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used for receiving data from a port on a MIDI device + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiOutputPort extends MidiPort implements MidiSender { + private static final String TAG = "MidiOutputPort"; + + private final FileInputStream mInputStream; + + // array of receiver lists, indexed by port number + private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>(); + + private int mReceiverCount; // total number of receivers for all ports + + // This thread reads MIDI events from a socket and distributes them to the list of + // MidiReceivers attached to this device. + private final Thread mThread = new Thread() { + @Override + public void run() { + byte[] buffer = new byte[MAX_PACKET_SIZE]; + ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>(); + + try { + while (true) { + // read next event + int count = mInputStream.read(buffer); + if (count < 0) { + break; + } + + int offset = getMessageOffset(buffer, count); + int size = getMessageSize(buffer, count); + long timestamp = getMessageTimeStamp(buffer, count); + + synchronized (mReceivers) { + for (int i = 0; i < mReceivers.size(); i++) { + MidiReceiver receiver = mReceivers.get(i); + try { + receiver.post(buffer, offset, size, timestamp); + } catch (IOException e) { + Log.e(TAG, "post failed"); + deadReceivers.add(receiver); + } + } + // remove any receivers that failed + if (deadReceivers.size() > 0) { + for (MidiReceiver receiver: deadReceivers) { + mReceivers.remove(receiver); + mReceiverCount--; + } + deadReceivers.clear(); + } + // exit if we have no receivers left + if (mReceiverCount == 0) { + break; + } + } + } + } catch (IOException e) { + // report I/O failure + Log.e(TAG, "read failed"); + } finally { + IoUtils.closeQuietly(mInputStream); + onIOException(); + } + } + }; + + /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) { + super(portNumber); + mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + } + + /** + * Connects a {@link MidiReceiver} to the output port to allow receiving + * MIDI messages from the port. + * + * @param receiver the receiver to connect + */ + public void connect(MidiReceiver receiver) { + synchronized (mReceivers) { + mReceivers.add(receiver); + if (mReceiverCount++ == 0) { + mThread.start(); + } + } + } + + /** + * Disconnects a {@link MidiReceiver} from the output port. + * + * @param receiver the receiver to connect + */ + public void disconnect(MidiReceiver receiver) { + synchronized (mReceivers) { + if (mReceivers.remove(receiver)) { + mReceiverCount--; + } + } + } + + @Override + public void close() throws IOException { + mInputStream.close(); + } +} diff --git a/media/java/android/media/midi/MidiPort.java b/media/java/android/media/midi/MidiPort.java new file mode 100644 index 0000000..4d3c91d --- /dev/null +++ b/media/java/android/media/midi/MidiPort.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import android.util.Log; + +import java.io.Closeable; + +/** + * This class represents a MIDI input or output port. + * Base class for {@link MidiInputPort} and {@link MidiOutputPort} + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +abstract public class MidiPort implements Closeable { + private static final String TAG = "MidiPort"; + + private final int mPortNumber; + + /** + * Maximum size of a packet that can pass through our ParcelFileDescriptor. + * For internal use only. Implementation details may change in the future. + * @hide + */ + public static final int MAX_PACKET_SIZE = 1024; + + /** + * size of message timestamp in bytes + * For internal use only. Implementation details may change in the future. + * @hide + */ + private static final int TIMESTAMP_SIZE = 8; + + /** + * Maximum amount of MIDI data that can be included in a packet + */ + public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE; + + + /* package */ MidiPort(int portNumber) { + mPortNumber = portNumber; + } + + /** + * Returns the port number of this port + * + * @return the port's port number + */ + public final int getPortNumber() { + return mPortNumber; + } + + /** + * Called when an IOExeption occurs while sending or receiving data. + * Subclasses can override to be notified of such errors + * + * @hide + */ + public void onIOException() { + } + + /** + * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor + * + * message byte array contains variable length MIDI message. + * messageSize is size of variable length MIDI message + * timestamp is message timestamp to pack + * dest is buffer to pack into + * returns size of packed message + * + * For internal use only. Implementation details may change in the future. + * @hide + */ + public static int packMessage(byte[] message, int offset, int size, long timestamp, + byte[] dest) { + if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) { + size = MAX_PACKET_SIZE - TIMESTAMP_SIZE; + } + // message data goes first + System.arraycopy(message, offset, dest, 0, size); + + // followed by timestamp + for (int i = 0; i < TIMESTAMP_SIZE; i++) { + dest[size++] = (byte)timestamp; + timestamp >>= 8; + } + + return size; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * returns the offset of the MIDI message in packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide + */ + public static int getMessageOffset(byte[] buffer, int bufferLength) { + // message is at the beginning + return 0; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * returns size of MIDI data in packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide + */ + public static int getMessageSize(byte[] buffer, int bufferLength) { + // message length is total buffer length minus size of the timestamp + return bufferLength - TIMESTAMP_SIZE; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * unpacks timestamp from packed buffer + * + * For internal use only. Implementation details may change in the future. + * @hide + */ + public static long getMessageTimeStamp(byte[] buffer, int bufferLength) { + // timestamp is at end of the packet + int offset = bufferLength; + long timestamp = 0; + + for (int i = 0; i < TIMESTAMP_SIZE; i++) { + int b = (int)buffer[--offset] & 0xFF; + timestamp = (timestamp << 8) | b; + } + return timestamp; + } +} diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java new file mode 100644 index 0000000..64c0c07 --- /dev/null +++ b/media/java/android/media/midi/MidiReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +import java.io.IOException; + +/** + * Interface for sending and receiving data to and from a MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public interface MidiReceiver { + /** + * Called to pass MIDI data to the receiver. + * + * NOTE: the msg array parameter is only valid within the context of this call. + * The msg bytes should be copied by the receiver rather than retaining a reference + * to this parameter. + * Also, modifying the contents of the msg array parameter may result in other receivers + * in the same application receiving incorrect values in their post() method. + * + * @param msg a byte array containing the MIDI data + * @param offset the offset of the first byte of the data in the byte array + * @param count the number of bytes of MIDI data in the array + * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime} + * @throws IOException + */ + public void post(byte[] msg, int offset, int count, long timestamp) throws IOException; +} diff --git a/media/java/android/media/midi/MidiSender.java b/media/java/android/media/midi/MidiSender.java new file mode 100644 index 0000000..4550476 --- /dev/null +++ b/media/java/android/media/midi/MidiSender.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.midi; + +/** + * Interface provided by a device to allow attaching + * MidiReceivers to a MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public interface MidiSender { + /** + * Called to connect a {@link MidiReceiver} to the sender + * + * @param receiver the receiver to connect + */ + public void connect(MidiReceiver receiver); + + /** + * Called to disconnect a {@link MidiReceiver} from the sender + * + * @param receiver the receiver to disconnect + */ + public void disconnect(MidiReceiver receiver); +} |