diff options
author | Mike Lockwood <lockwood@google.com> | 2015-03-05 00:00:31 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-03-05 00:01:33 +0000 |
commit | c623ec973b5e8d1fd2ff4162d5de8701a6490121 (patch) | |
tree | d78b361229b131434f900b11b35e4df635c351b3 /media | |
parent | ef7aa4fc1901d5413bfa9a05f0a6c4f4d2d6575e (diff) | |
parent | 11fd96d6ff25bc1d710448eab545fe09da55a5f5 (diff) | |
download | frameworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.zip frameworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.tar.gz frameworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.tar.bz2 |
Merge "MidiManager: Virtual MIDI devices are now implemented as Services"
Diffstat (limited to 'media')
-rw-r--r-- | media/java/android/media/midi/IMidiDeviceListener.aidl (renamed from media/java/android/media/midi/IMidiListener.aidl) | 2 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiManager.aidl | 18 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceInfo.java | 34 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceServer.java | 254 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceService.java | 111 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDispatcher.java | 89 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiManager.java | 116 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiOutputPort.java | 66 |
8 files changed, 428 insertions, 262 deletions
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl index a4129e9..17d9bfd 100644 --- a/media/java/android/media/midi/IMidiListener.aidl +++ b/media/java/android/media/midi/IMidiDeviceListener.aidl @@ -19,7 +19,7 @@ package android.media.midi; import android.media.midi.MidiDeviceInfo; /** @hide */ -oneway interface IMidiListener +oneway interface IMidiDeviceListener { 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 index bba35f5..617b03e 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -16,8 +16,8 @@ package android.media.midi; +import android.media.midi.IMidiDeviceListener; import android.media.midi.IMidiDeviceServer; -import android.media.midi.IMidiListener; import android.media.midi.MidiDeviceInfo; import android.os.Bundle; import android.os.IBinder; @@ -28,14 +28,20 @@ interface IMidiManager MidiDeviceInfo[] getDeviceList(); // for device creation & removal notifications - void registerListener(IBinder token, in IMidiListener listener); - void unregisterListener(IBinder token, in IMidiListener listener); + void registerListener(IBinder token, in IMidiDeviceListener listener); + void unregisterListener(IBinder token, in IMidiDeviceListener listener); - // for communicating with MIDI devices + // for opening built-in MIDI devices IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); - // for implementing virtual MIDI devices + // for registering built-in MIDI devices MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, - int numOutputPorts, in Bundle properties, boolean isPrivate, int type); + int numOutputPorts, in Bundle properties, int type); + + // for unregistering built-in MIDI devices void unregisterDeviceServer(in IMidiDeviceServer server); + + // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its + // manifest's meta-data + MidiDeviceInfo getServiceDeviceInfo(String packageName, String className); } diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index fd35052..b7756fd 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -50,6 +50,7 @@ public class MidiDeviceInfo implements Parcelable { private final int mInputPortCount; private final int mOutputPortCount; private final Bundle mProperties; + private final boolean mIsPrivate; /** * Bundle key for the device's manufacturer name property. @@ -83,6 +84,8 @@ public class MidiDeviceInfo implements Parcelable { * 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} + * + * @hide */ public static final String PROPERTY_ALSA_CARD = "alsa_card"; @@ -90,20 +93,32 @@ public class MidiDeviceInfo implements Parcelable { * 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} + * + * @hide */ public static final String PROPERTY_ALSA_DEVICE = "alsa_device"; /** + * {@link android.content.pm.ServiceInfo} for the service hosting the device implementation. + * Only set for Virtual MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + * + * @hide + */ + public static final String PROPERTY_SERVICE_INFO = "service_info"; + + /** * MidiDeviceInfo should only be instantiated by MidiService implementation * @hide */ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, - Bundle properties) { + Bundle properties, boolean isPrivate) { mType = type; mId = id; mInputPortCount = numInputPorts; mOutputPortCount = numOutputPorts; mProperties = properties; + mIsPrivate = isPrivate; } /** @@ -152,6 +167,16 @@ public class MidiDeviceInfo implements Parcelable { return mProperties; } + /** + * Returns true if the device is private. Private devices are only visible and accessible + * to clients with the same UID as the application that is hosting the device. + * + * @return true if the device is private + */ + public boolean isPrivate() { + return mIsPrivate; + } + @Override public boolean equals(Object o) { if (o instanceof MidiDeviceInfo) { @@ -171,7 +196,8 @@ public class MidiDeviceInfo implements Parcelable { return ("MidiDeviceInfo[mType=" + mType + ",mInputPortCount=" + mInputPortCount + ",mOutputPortCount=" + mOutputPortCount + - ",mProperties=" + mProperties); + ",mProperties=" + mProperties + + ",mIsPrivate=" + mIsPrivate); } public static final Parcelable.Creator<MidiDeviceInfo> CREATOR = @@ -182,7 +208,8 @@ public class MidiDeviceInfo implements Parcelable { int inputPorts = in.readInt(); int outputPorts = in.readInt(); Bundle properties = in.readBundle(); - return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties); + boolean isPrivate = (in.readInt() == 1); + return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, isPrivate); } public MidiDeviceInfo[] newArray(int size) { @@ -200,5 +227,6 @@ public class MidiDeviceInfo implements Parcelable { parcel.writeInt(mInputPortCount); parcel.writeInt(mOutputPortCount); parcel.writeBundle(mProperties); + parcel.writeInt(mIsPrivate ? 1 : 0); } } diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java index 3317baa..24ef528 100644 --- a/media/java/android/media/midi/MidiDeviceServer.java +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -16,21 +16,22 @@ package android.media.midi; +import android.os.IBinder; +import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.system.OsConstants; import android.util.Log; +import libcore.io.IoUtils; + 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. + * Internal class used for providing an implementation for a MIDI device. * - * CANDIDATE FOR PUBLIC API * @hide */ public final class MidiDeviceServer implements Closeable { @@ -40,64 +41,36 @@ public final class MidiDeviceServer implements Closeable { // 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; + private final int mInputPortCount; + private final int mOutputPortCount; - // receivers attached to our input ports - private ArrayList<MidiReceiver>[] mInputPortReceivers; + // MidiReceivers for receiving data on our input ports + private final MidiReceiver[] mInputPortReceivers; - // input ports for sending messages to our clients - // we can have multiple outputs per port number - private ArrayList<MidiInputPort>[] mOutputPortReceivers; + // MidiDispatchers for sending data on our output ports + private MidiDispatcher[] mOutputPortDispatchers; - // 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; - } - } - } + // MidiOutputPorts for clients connected to our input ports + private final MidiOutputPort[] mInputPortOutputPorts; // Binder interface stub for receiving connection requests from clients private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { @Override public ParcelFileDescriptor openInputPort(int portNumber) { + if (mDeviceInfo.isPrivate()) { + if (Binder.getCallingUid() != Process.myUid()) { + throw new SecurityException("Can't access private device from different UID"); + } + } + 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) { + synchronized (mInputPortOutputPorts) { + if (mInputPortOutputPorts[portNumber] != null) { Log.d(TAG, "port " + portNumber + " already open"); return null; } @@ -105,47 +78,86 @@ public final class MidiDeviceServer implements Closeable { 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)); + final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber); + mInputPortOutputPorts[portNumber] = outputPort; + final int portNumberF = portNumber; + final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber]; + + outputPort.connect(new MidiReceiver() { + @Override + public void post(byte[] msg, int offset, int count, long timestamp) + throws IOException { + try { + inputPortReceviver.post(msg, offset, count, timestamp); + } catch (IOException e) { + IoUtils.closeQuietly(mInputPortOutputPorts[portNumberF]); + mInputPortOutputPorts[portNumberF] = null; + // FIXME also flush the receiver + } } - } + }); + + return pair[1]; } catch (IOException e) { Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); return null; } } - - return result; } @Override public ParcelFileDescriptor openOutputPort(int portNumber) { + if (mDeviceInfo.isPrivate()) { + if (Binder.getCallingUid() != Process.myUid()) { + throw new SecurityException("Can't access private device from different UID"); + } + } + 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; - } + + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + final MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber); + final MidiSender sender = mOutputPortDispatchers[portNumber].getSender(); + sender.connect(new MidiReceiver() { + @Override + public void post(byte[] msg, int offset, int count, long timestamp) + throws IOException { + try { + inputPort.post(msg, offset, count, timestamp); + } catch (IOException e) { + IoUtils.closeQuietly(inputPort); + sender.disconnect(this); + // FIXME also flush the receiver? + } + } + }); + + return pair[1]; + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); + return null; } } }; - /* package */ MidiDeviceServer(IMidiManager midiManager) { + /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, + int numOutputPorts) { mMidiManager = midiManager; + mInputPortReceivers = inputPortReceivers; + mInputPortCount = inputPortReceivers.length; + mOutputPortCount = numOutputPorts; + + mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; + + mOutputPortDispatchers = new MidiDispatcher[numOutputPorts]; + for (int i = 0; i < numOutputPorts; i++) { + mOutputPortDispatchers[i] = new MidiDispatcher(); + } } /* package */ IMidiDeviceServer getBinderInterface() { @@ -157,19 +169,6 @@ public final class MidiDeviceServer implements Closeable { 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 @@ -183,86 +182,13 @@ public final class MidiDeviceServer implements Closeable { } /** - * 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} + * Returns an array of {@link MidiReceiver} for the device's output ports. + * Clients can use these receivers to send data out the device's output ports. + * @return array of MidiReceivers */ - 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); - } - } - } - }; + public MidiReceiver[] getOutputPortReceivers() { + MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount]; + System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount); + return receivers; } } diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java new file mode 100644 index 0000000..1d91be2 --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceService.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 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.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +/** + * A service that implements a virtual MIDI device. + * Subclasses must implement the {@link #getInputPortReceivers} method to provide a + * list of {@link MidiReceiver}s to receive data sent to the device's input ports. + * Similarly, subclasses can call {@link #getOutputPortReceivers} to fetch a list + * of {@link MidiReceiver}s for sending data out the output ports. + * + * <p>To extend this class, you must declare the service in your manifest file with + * an intent filter with the {@link #SERVICE_INTERFACE} action + * and meta-data to describe the virtual device. + For example:</p> + * <pre> + * <service android:name=".VirtualDeviceService" + * android:label="@string/service_name"> + * <intent-filter> + * <action android:name="android.media.midi.MidiDeviceService" /> + * </intent-filter> + * <meta-data android:name="android.media.midi.MidiDeviceService" + android:resource="@xml/device_info" /> + * </service></pre> + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +abstract public class MidiDeviceService extends Service { + private static final String TAG = "MidiDeviceService"; + + public static final String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService"; + + private IMidiManager mMidiManager; + private MidiDeviceServer mServer; + + @Override + public void onCreate() { + mMidiManager = IMidiManager.Stub.asInterface( + ServiceManager.getService(Context.MIDI_SERVICE)); + MidiDeviceServer server; + try { + MidiDeviceInfo deviceInfo = mMidiManager.getServiceDeviceInfo(getPackageName(), + this.getClass().getName()); + MidiReceiver[] inputPortReceivers = getInputPortReceivers(); + if (inputPortReceivers == null) { + inputPortReceivers = new MidiReceiver[0]; + } + server = new MidiDeviceServer(mMidiManager, inputPortReceivers, + deviceInfo.getOutputPortCount()); + server.setDeviceInfo(deviceInfo); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo"); + server = null; + } + mServer = server; + } + + /** + * Returns an array of {@link MidiReceiver} for the device's input ports. + * Subclasses must override this to provide the receivers which will receive + * data sent to the device's input ports. An empty array or null should be returned if + * the device has no input ports. + * @return array of MidiReceivers + */ + abstract public MidiReceiver[] getInputPortReceivers(); + + /** + * Returns an array of {@link MidiReceiver} for the device's output ports. + * These can be used to send data out the device's output ports. + * @return array of MidiReceivers + */ + public MidiReceiver[] getOutputPortReceivers() { + if (mServer == null) { + return null; + } else { + return mServer.getOutputPortReceivers(); + } + } + + @Override + public IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { + return mServer.getBinderInterface().asBinder(); + } else { + return null; + } + } +} diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java new file mode 100644 index 0000000..165061f --- /dev/null +++ b/media/java/android/media/midi/MidiDispatcher.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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; +import java.util.ArrayList; + +/** + * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s. + * This class subclasses {@link MidiReceiver} and dispatches any data it receives + * to its receiver list. Any receivers that throw an exception upon receiving data will + * be automatically removed from the receiver list, but no IOException will be returned + * from the dispatcher's {@link #post} in that case. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiDispatcher implements MidiReceiver { + + private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>(); + + private final MidiSender mSender = new MidiSender() { + /** + * Called to connect a {@link MidiReceiver} to the sender + * + * @param receiver the receiver to connect + */ + public void connect(MidiReceiver receiver) { + mReceivers.add(receiver); + } + + /** + * Called to disconnect a {@link MidiReceiver} from the sender + * + * @param receiver the receiver to disconnect + */ + public void disconnect(MidiReceiver receiver) { + mReceivers.remove(receiver); + } + }; + + /** + * Returns whether this dispatcher contains any receivers. + * @return true if the receiver list is not empty + */ + public boolean hasReceivers() { + return mReceivers.size() > 0; + } + + /** + * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s + * to the dispatcher's receiver list. + * @return the dispatcher's MidiSender + */ + public MidiSender getSender() { + return mSender; + } + + @Override + public void post(byte[] msg, int offset, int count, long timestamp) throws IOException { + synchronized (mReceivers) { + for (int i = 0; i < mReceivers.size(); ) { + MidiReceiver receiver = mReceivers.get(i); + try { + receiver.post(msg, offset, count, timestamp); + i++; // increment only on success. on failure we remove the receiver + // so i should not be incremented + } catch (IOException e) { + // if the receiver fails we remove the receiver but do not propogate the exception + mSender.disconnect(receiver); + } + } + } + } +} diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index a502e3e..ca7d3c2 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -16,7 +16,11 @@ package android.media.midi; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.Bundle; @@ -49,7 +53,7 @@ public class MidiManager { new HashMap<DeviceCallback,DeviceListener>(); // Binder stub for receiving device notifications from MidiService - private class DeviceListener extends IMidiListener.Stub { + private class DeviceListener extends IMidiDeviceListener.Stub { private final DeviceCallback mCallback; private final Handler mHandler; @@ -88,22 +92,33 @@ public class MidiManager { /** * Callback class used for clients to receive MIDI device added and removed notifications */ - public static class DeviceCallback { + abstract 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) { - } + abstract 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) { - } + abstract public void onDeviceRemoved(MidiDeviceInfo device); + } + + /** + * Callback class used for receiving the results of {@link #openDevice} + */ + abstract public static class DeviceOpenCallback { + /** + * Called to respond to a {@link #openDevice} request + * + * @param deviceInfo the {@link MidiDeviceInfo} for the device to open + * @param device a {@link MidiDevice} for opened device, or null if opening failed + */ + abstract public void onDeviceOpened(MidiDeviceInfo deviceInfo, MidiDevice device); } /** @@ -163,33 +178,85 @@ public class MidiManager { } } + private void sendOpenDeviceResponse(final MidiDeviceInfo deviceInfo, final MidiDevice device, + final DeviceOpenCallback callback, Handler handler) { + if (handler != null) { + handler.post(new Runnable() { + @Override public void run() { + callback.onDeviceOpened(deviceInfo, device); + } + }); + } else { + callback.onDeviceOpened(deviceInfo, device); + } + } + /** * 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 + * @param callback a {@link #DeviceOpenCallback} to be called to receive the result + * @param handler the {@link android.os.Handler Handler} that will be used for delivering + * the result. If handler is null, then the thread used for the + * callback is unspecified. */ - public MidiDevice openDevice(MidiDeviceInfo deviceInfo) { + public void openDevice(MidiDeviceInfo deviceInfo, DeviceOpenCallback callback, + Handler handler) { + MidiDevice device = null; try { IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); if (server == null) { - Log.e(TAG, "could not open device " + deviceInfo); - return null; + ServiceInfo serviceInfo = (ServiceInfo)deviceInfo.getProperties().getParcelable( + MidiDeviceInfo.PROPERTY_SERVICE_INFO); + if (serviceInfo == null) { + Log.e(TAG, "no ServiceInfo for " + deviceInfo); + } else { + Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); + intent.setComponent(new ComponentName(serviceInfo.packageName, + serviceInfo.name)); + final MidiDeviceInfo deviceInfoF = deviceInfo; + final DeviceOpenCallback callbackF = callback; + final Handler handlerF = handler; + if (mContext.bindService(intent, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + IMidiDeviceServer server = + IMidiDeviceServer.Stub.asInterface(binder); + MidiDevice device = new MidiDevice(deviceInfoF, server); + sendOpenDeviceResponse(deviceInfoF, device, callbackF, handlerF); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + // FIXME - anything to do here? + } + }, + Context.BIND_AUTO_CREATE)) + { + // return immediately to avoid calling sendOpenDeviceResponse below + return; + } else { + Log.e(TAG, "Unable to bind service: " + intent); + } + } + } else { + device = new MidiDevice(deviceInfo, server); } - return new MidiDevice(deviceInfo, server); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openDevice"); } - return null; + sendOpenDeviceResponse(deviceInfo, device, callback, handler); } /** @hide */ - public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, - Bundle properties, boolean isPrivate, int type) { + public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, + int numOutputPorts, Bundle properties, int type) { try { - MidiDeviceServer server = new MidiDeviceServer(mService); + MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, + numOutputPorts); MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), - numInputPorts, numOutputPorts, properties, isPrivate, type); + inputPortReceivers.length, numOutputPorts, properties, type); if (deviceInfo == null) { Log.e(TAG, "registerVirtualDevice failed"); return null; @@ -201,21 +268,4 @@ public class MidiManager { 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 index 83ddeeb..c195603 100644 --- a/media/java/android/media/midi/MidiOutputPort.java +++ b/media/java/android/media/midi/MidiOutputPort.java @@ -23,7 +23,6 @@ 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 @@ -35,11 +34,7 @@ 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 + private final MidiDispatcher mDispatcher = new MidiDispatcher(); // This thread reads MIDI events from a socket and distributes them to the list of // MidiReceivers attached to this device. @@ -47,7 +42,6 @@ public class MidiOutputPort extends MidiPort implements MidiSender { @Override public void run() { byte[] buffer = new byte[MAX_PACKET_SIZE]; - ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>(); try { while (true) { @@ -55,77 +49,39 @@ public class MidiOutputPort extends MidiPort implements MidiSender { int count = mInputStream.read(buffer); if (count < 0) { break; + // FIXME - inform receivers here? } 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; - } - } + // dispatch to all our receivers + mDispatcher.post(buffer, offset, size, timestamp); } } catch (IOException e) { - // report I/O failure + // FIXME report I/O failure? Log.e(TAG, "read failed"); } finally { IoUtils.closeQuietly(mInputStream); - onIOException(); } } }; - /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) { + /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) { super(portNumber); mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + mThread.start(); } - /** - * Connects a {@link MidiReceiver} to the output port to allow receiving - * MIDI messages from the port. - * - * @param receiver the receiver to connect - */ + @Override public void connect(MidiReceiver receiver) { - synchronized (mReceivers) { - mReceivers.add(receiver); - if (mReceiverCount++ == 0) { - mThread.start(); - } - } + mDispatcher.getSender().connect(receiver); } - /** - * Disconnects a {@link MidiReceiver} from the output port. - * - * @param receiver the receiver to connect - */ + @Override public void disconnect(MidiReceiver receiver) { - synchronized (mReceivers) { - if (mReceivers.remove(receiver)) { - mReceiverCount--; - } - } + mDispatcher.getSender().disconnect(receiver); } @Override |