diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 2 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiDeviceOpenCallback.aidl | 25 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiDeviceServer.aidl | 1 | ||||
-rw-r--r-- | media/java/android/media/midi/IMidiManager.aidl | 15 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDevice.java | 27 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceServer.java | 16 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiDeviceService.java | 11 | ||||
-rw-r--r-- | media/java/android/media/midi/MidiManager.java | 118 | ||||
-rw-r--r-- | media/java/android/media/midi/package.html | 6 | ||||
-rw-r--r-- | media/packages/BluetoothMidiService/AndroidManifest.xml | 3 | ||||
-rw-r--r-- | media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java | 26 | ||||
-rw-r--r-- | services/midi/java/com/android/server/midi/MidiService.java | 434 |
16 files changed, 520 insertions, 174 deletions
@@ -343,6 +343,7 @@ LOCAL_SRC_FILES += \ media/java/android/media/IVolumeController.aidl \ media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \ media/java/android/media/midi/IMidiDeviceListener.aidl \ + media/java/android/media/midi/IMidiDeviceOpenCallback.aidl \ media/java/android/media/midi/IMidiDeviceServer.aidl \ media/java/android/media/midi/IMidiManager.aidl \ media/java/android/media/projection/IMediaProjection.aidl \ diff --git a/api/current.txt b/api/current.txt index 49e2268..c3761ad 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28,6 +28,7 @@ package android { field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"; field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; + field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE"; field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; @@ -17360,6 +17361,7 @@ package android.media.midi { method public final android.media.midi.MidiDeviceInfo getDeviceInfo(); method public final android.media.midi.MidiReceiver[] getOutputPortReceivers(); method public android.os.IBinder onBind(android.content.Intent); + method public void onClose(); method public void onDeviceStatusChanged(android.media.midi.MidiDeviceStatus); method public abstract android.media.midi.MidiReceiver[] onGetInputPortReceivers(); field public static final java.lang.String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService"; diff --git a/api/system-current.txt b/api/system-current.txt index 3e8b065..43f7f95 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -40,6 +40,7 @@ package android { field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; + field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE"; field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; @@ -18672,6 +18673,7 @@ package android.media.midi { method public final android.media.midi.MidiDeviceInfo getDeviceInfo(); method public final android.media.midi.MidiReceiver[] getOutputPortReceivers(); method public android.os.IBinder onBind(android.content.Intent); + method public void onClose(); method public void onDeviceStatusChanged(android.media.midi.MidiDeviceStatus); method public abstract android.media.midi.MidiReceiver[] onGetInputPortReceivers(); field public static final java.lang.String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService"; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 0d00908..10e8a53 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -691,7 +691,7 @@ final class SystemServiceRegistry { @Override public MidiManager createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(Context.MIDI_SERVICE); - return new MidiManager(ctx, IMidiManager.Stub.asInterface(b)); + return new MidiManager(IMidiManager.Stub.asInterface(b)); }}); registerService(Context.RADIO_SERVICE, RadioManager.class, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f32918d..709de9e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1827,6 +1827,11 @@ <permission android:name="android.permission.BIND_INPUT_METHOD" android:protectionLevel="signature" /> + <!-- Must be required by an {@link android.media.midi.MidiDeviceService}, + to ensure that only the system can bind to it. --> + <permission android:name="android.permission.BIND_MIDI_DEVICE_SERVICE" + android:protectionLevel="signature" /> + <!-- Must be required by an {@link android.accessibilityservice.AccessibilityService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" diff --git a/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl b/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl new file mode 100644 index 0000000..6e3dbbf --- /dev/null +++ b/media/java/android/media/midi/IMidiDeviceOpenCallback.aidl @@ -0,0 +1,25 @@ +/* + * 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.media.midi.IMidiDeviceServer; + +/** @hide */ +oneway interface IMidiDeviceOpenCallback +{ + void onDeviceOpened(in IMidiDeviceServer server, IBinder token); +} diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl index 96d12fd..e30796d 100644 --- a/media/java/android/media/midi/IMidiDeviceServer.aidl +++ b/media/java/android/media/midi/IMidiDeviceServer.aidl @@ -25,6 +25,7 @@ interface IMidiDeviceServer ParcelFileDescriptor openInputPort(IBinder token, int portNumber); ParcelFileDescriptor openOutputPort(IBinder token, int portNumber); void closePort(IBinder token); + void closeDevice(); // connects the input port pfd to the specified output port void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber); diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index fcd4aff..d3c8e0a 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -16,7 +16,10 @@ package android.media.midi; +import android.bluetooth.BluetoothDevice; import android.media.midi.IMidiDeviceListener; +import android.media.midi.IMidiDeviceOpenCallback; +import android.media.midi.IMidiDeviceServer; import android.media.midi.IMidiDeviceServer; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceStatus; @@ -29,11 +32,13 @@ interface IMidiManager MidiDeviceInfo[] getDevices(); // for device creation & removal notifications - void registerListener(IBinder token, in IMidiDeviceListener listener); - void unregisterListener(IBinder token, in IMidiDeviceListener listener); + void registerListener(IBinder clientToken, in IMidiDeviceListener listener); + void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener); - // for opening built-in MIDI devices - IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); + void openDevice(IBinder clientToken, in MidiDeviceInfo device, in IMidiDeviceOpenCallback callback); + void openBluetoothDevice(IBinder clientToken, in BluetoothDevice bluetoothDevice, + in IMidiDeviceOpenCallback callback); + void closeDevice(IBinder clientToken, IBinder deviceToken); // for registering built-in MIDI devices MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, @@ -52,5 +57,5 @@ interface IMidiManager // used by MIDI devices to report their status // the token is used by MidiService for death notification - void setDeviceStatus(IBinder token, in MidiDeviceStatus status); + void setDeviceStatus(in IMidiDeviceServer server, in MidiDeviceStatus status); } diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java index 6526adc..7998a92 100644 --- a/media/java/android/media/midi/MidiDevice.java +++ b/media/java/android/media/midi/MidiDevice.java @@ -16,8 +16,6 @@ package android.media.midi; -import android.content.Context; -import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -40,9 +38,9 @@ public final class MidiDevice implements Closeable { private final MidiDeviceInfo mDeviceInfo; private final IMidiDeviceServer mDeviceServer; - private Context mContext; - private ServiceConnection mServiceConnection; - + private final IMidiManager mMidiManager; + private final IBinder mClientToken; + private final IBinder mDeviceToken; private final CloseGuard mGuard = CloseGuard.get(); @@ -71,16 +69,13 @@ public final class MidiDevice implements Closeable { } } - /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) { - this(deviceInfo, server, null, null); - } - /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server, - Context context, ServiceConnection serviceConnection) { + IMidiManager midiManager, IBinder clientToken, IBinder deviceToken) { mDeviceInfo = deviceInfo; mDeviceServer = server; - mContext = context; - mServiceConnection = serviceConnection; + mMidiManager = midiManager; + mClientToken = clientToken; + mDeviceToken = deviceToken; mGuard.open("close"); } @@ -170,10 +165,10 @@ public final class MidiDevice implements Closeable { public void close() throws IOException { synchronized (mGuard) { mGuard.close(); - if (mContext != null && mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - mContext = null; - mServiceConnection = null; + try { + mMidiManager.closeDevice(mClientToken, mDeviceToken); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in closeDevice"); } } } diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java index a316a44..1b56e1c 100644 --- a/media/java/android/media/midi/MidiDeviceServer.java +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -65,7 +65,6 @@ public final class MidiDeviceServer implements Closeable { // for reporting device status - private final IBinder mDeviceStatusToken = new Binder(); private final boolean[] mInputPortOpen; private final int[] mOutputPortOpenCount; @@ -81,6 +80,11 @@ public final class MidiDeviceServer implements Closeable { * @param status the {@link MidiDeviceStatus} for the device */ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status); + + /** + * Called to notify when the device is closed + */ + public void onClose(); } abstract private class PortClient implements IBinder.DeathRecipient { @@ -242,6 +246,14 @@ public final class MidiDeviceServer implements Closeable { } @Override + public void closeDevice() { + if (mCallback != null) { + mCallback.onClose(); + } + IoUtils.closeQuietly(MidiDeviceServer.this); + } + + @Override public void connectPorts(IBinder token, ParcelFileDescriptor pfd, int outputPortNumber) { MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber); @@ -305,7 +317,7 @@ public final class MidiDeviceServer implements Closeable { mCallback.onDeviceStatusChanged(this, status); } try { - mMidiManager.setDeviceStatus(mDeviceStatusToken, status); + mMidiManager.setDeviceStatus(mServer, status); } catch (RemoteException e) { Log.e(TAG, "RemoteException in updateDeviceStatus"); } finally { diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java index ce12a4f..d897ad2 100644 --- a/media/java/android/media/midi/MidiDeviceService.java +++ b/media/java/android/media/midi/MidiDeviceService.java @@ -59,6 +59,11 @@ abstract public class MidiDeviceService extends Service { public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { MidiDeviceService.this.onDeviceStatusChanged(status); } + + @Override + public void onClose() { + MidiDeviceService.this.onClose(); + } }; @Override @@ -125,6 +130,12 @@ abstract public class MidiDeviceService extends Service { public void onDeviceStatusChanged(MidiDeviceStatus status) { } + /** + * Called to notify when our device has been closed by all its clients + */ + public void onClose() { + } + @Override public IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index d19cf36..0beb9a4 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -17,11 +17,6 @@ package android.media.midi; import android.bluetooth.BluetoothDevice; -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; @@ -52,16 +47,17 @@ public final class MidiManager { /** * BluetoothMidiService package name + * @hide */ - private static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice"; + public static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice"; /** * BluetoothMidiService class name + * @hide */ - private static final String BLUETOOTH_MIDI_SERVICE_CLASS = + public static final String BLUETOOTH_MIDI_SERVICE_CLASS = "com.android.bluetoothmidiservice.BluetoothMidiService"; - private final Context mContext; private final IMidiManager mService; private final IBinder mToken = new Binder(); @@ -166,8 +162,7 @@ public final class MidiManager { /** * @hide */ - public MidiManager(Context context, IMidiManager service) { - mContext = context; + public MidiManager(IMidiManager service) { mService = service; } @@ -237,7 +232,7 @@ public final class MidiManager { * Opens a MIDI device for reading and writing. * * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open - * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called + * @param listener a {@link MidiManager.OnDeviceOpenedListener} 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 @@ -245,52 +240,28 @@ public final class MidiManager { */ public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler) { - MidiDevice device = null; - try { - IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); - if (server == 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 OnDeviceOpenedListener listenerF = listener; - 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, mContext, - this); - sendOpenDeviceResponse(device, listenerF, handlerF); - } + final MidiDeviceInfo deviceInfoF = deviceInfo; + final OnDeviceOpenedListener listenerF = listener; + final Handler handlerF = handler; - @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); - } + IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { + @Override + public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + MidiDevice device; + if (server != null) { + device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken); + } else { + device = null; } - } else { - device = new MidiDevice(deviceInfo, server); + sendOpenDeviceResponse(device, listenerF, handlerF); } + }; + + try { + mService.openDevice(mToken, deviceInfo, callback); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openDevice"); } - sendOpenDeviceResponse(device, listener, handler); } /** @@ -303,38 +274,33 @@ public final class MidiManager { * the result. If handler is null, then the thread used for the * listener is unspecified. */ - public void openBluetoothDevice(final BluetoothDevice bluetoothDevice, - final OnDeviceOpenedListener listener, final Handler handler) { - Intent intent = new Intent(BLUETOOTH_MIDI_SERVICE_INTENT); - intent.setComponent(new ComponentName(BLUETOOTH_MIDI_SERVICE_PACKAGE, - BLUETOOTH_MIDI_SERVICE_CLASS)); - intent.putExtra("device", bluetoothDevice); - if (!mContext.bindService(intent, - new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - IMidiDeviceServer server = - IMidiDeviceServer.Stub.asInterface(binder); + public void openBluetoothDevice(BluetoothDevice bluetoothDevice, + OnDeviceOpenedListener listener, Handler handler) { + final OnDeviceOpenedListener listenerF = listener; + final Handler handlerF = handler; + + IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { + @Override + public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + MidiDevice device = null; + if (server != null) { try { // fetch MidiDeviceInfo from the server MidiDeviceInfo deviceInfo = server.getDeviceInfo(); - MidiDevice device = new MidiDevice(deviceInfo, server, mContext, this); - sendOpenDeviceResponse(device, listener, handler); + device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken); + sendOpenDeviceResponse(device, listenerF, handlerF); } catch (RemoteException e) { - Log.e(TAG, "remote exception in onServiceConnected"); - sendOpenDeviceResponse(null, listener, handler); + Log.e(TAG, "remote exception in getDeviceInfo()"); } } + sendOpenDeviceResponse(device, listenerF, handlerF); + } + }; - @Override - public void onServiceDisconnected(ComponentName name) { - // FIXME - anything to do here? - } - }, - Context.BIND_AUTO_CREATE)) - { - Log.e(TAG, "Unable to bind service: " + intent); - sendOpenDeviceResponse(null, listener, handler); + try { + mService.openBluetoothDevice(mToken, bluetoothDevice, callback); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openDevice"); } } diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index bd589a9..8b2bd16 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -241,7 +241,8 @@ messages.</p> <p>An app can provide a MIDI Service that can be used by other apps. For example, -an app can provide a custom synthesizer that other apps can send messages to. </p> +an app can provide a custom synthesizer that other apps can send messages to. +The service must be guarded with permission "android.permission.BIND_MIDI_DEVICE_SERVICE".</p> <h2 id=manifest_files>Manifest Files</h2> @@ -250,7 +251,8 @@ an app can provide a custom synthesizer that other apps can send messages to. </ AndroidManifest.xml file.</p> <pre class=prettyprint> -<service android:name="<strong>MySynthDeviceService</strong>"> +<service android:name="<strong>MySynthDeviceService</strong>" + android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.MidiDeviceService" /> </intent-filter> diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index 15aa581..b0b389a 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -8,7 +8,8 @@ <application android:label="@string/app_name"> - <service android:name="BluetoothMidiService"> + <service android:name="BluetoothMidiService" + android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.BluetoothMidiService" /> </intent-filter> diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 60c6570..e6d59e4 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -24,10 +24,11 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.media.midi.MidiReceiver; -import android.media.midi.MidiManager; -import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiManager; +import android.media.midi.MidiReceiver; import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -81,6 +82,18 @@ public final class BluetoothMidiDevice { private final BluetoothPacketDecoder mPacketDecoder = new BluetoothPacketDecoder(MAX_PACKET_SIZE); + private final MidiDeviceServer.Callback mDeviceServerCallback + = new MidiDeviceServer.Callback() { + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + } + + @Override + public void onClose() { + close(); + } + }; + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, @@ -213,7 +226,7 @@ public final class BluetoothMidiDevice { inputPortReceivers[0] = mEventScheduler.getReceiver(); mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, - null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null); + null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback); mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; @@ -248,11 +261,12 @@ public final class BluetoothMidiDevice { private void close() { synchronized (mBluetoothDevice) { - mEventScheduler.close(); + mEventScheduler.close(); + mService.deviceClosed(mBluetoothDevice); + if (mDeviceServer != null) { IoUtils.closeQuietly(mDeviceServer); mDeviceServer = null; - mService.deviceClosed(mBluetoothDevice); } if (mBluetoothGatt != null) { mBluetoothGatt.close(); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 370f125..d1bbbfc 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -16,8 +16,11 @@ package com.android.server.midi; +import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -25,11 +28,13 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.media.midi.IMidiDeviceListener; +import android.media.midi.IMidiDeviceOpenCallback; import android.media.midi.IMidiDeviceServer; import android.media.midi.IMidiManager; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceService; import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiManager; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -78,6 +83,10 @@ public class MidiService extends IMidiManager.Stub { private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo = new HashMap<MidiDeviceInfo, Device>(); + // list of all Bluetooth devices, keyed by BluetoothDevice + private final HashMap<BluetoothDevice, Device> mBluetoothDevices + = new HashMap<BluetoothDevice, Device>(); + // list of all devices, keyed by IMidiDeviceServer private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>(); @@ -86,6 +95,9 @@ public class MidiService extends IMidiManager.Stub { private final PackageManager mPackageManager; + // UID of BluetoothMidiService + private final int mBluetoothServiceUid; + // PackageMonitor for listening to package changes private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override @@ -115,6 +127,9 @@ public class MidiService extends IMidiManager.Stub { // List of all receivers for this client private final ArrayList<IMidiDeviceListener> mListeners = new ArrayList<IMidiDeviceListener>(); + // List of all device connections for this client + private final HashMap<IBinder, DeviceConnection> mDeviceConnections + = new HashMap<IBinder, DeviceConnection>(); public Client(IBinder token) { mToken = token; @@ -132,8 +147,33 @@ public class MidiService extends IMidiManager.Stub { public void removeListener(IMidiDeviceListener listener) { mListeners.remove(listener); - if (mListeners.size() == 0) { - removeClient(mToken); + if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { + close(); + } + } + + public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) { + DeviceConnection connection = new DeviceConnection(device, this, callback); + mDeviceConnections.put(connection.getToken(), connection); + device.addDeviceConnection(connection); + } + + // called from MidiService.closeDevice() + public void removeDeviceConnection(IBinder token) { + DeviceConnection connection = mDeviceConnections.remove(token); + if (connection != null) { + connection.getDevice().removeDeviceConnection(connection); + } + if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { + close(); + } + } + + // called from Device.close() + public void removeDeviceConnection(DeviceConnection connection) { + mDeviceConnections.remove(connection.getToken()); + if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { + close(); } } @@ -178,8 +218,21 @@ public class MidiService extends IMidiManager.Stub { } } + private void close() { + synchronized (mClients) { + mClients.remove(mToken); + mToken.unlinkToDeath(this, 0); + } + + for (DeviceConnection connection : mDeviceConnections.values()) { + connection.getDevice().removeDeviceConnection(connection); + } + } + + @Override public void binderDied() { - removeClient(mToken); + Log.d(TAG, "Client died: " + this); + close(); } @Override @@ -190,6 +243,12 @@ public class MidiService extends IMidiManager.Stub { sb.append(mPid); sb.append(" listener count: "); sb.append(mListeners.size()); + sb.append(" Device Connections:"); + for (DeviceConnection connection : mDeviceConnections.values()) { + sb.append(" <device "); + sb.append(connection.getDevice().getDeviceInfo().getId()); + sb.append(">"); + } return sb.toString(); } } @@ -211,57 +270,96 @@ public class MidiService extends IMidiManager.Stub { } } - private void removeClient(IBinder token) { - mClients.remove(token); - } - private final class Device implements IBinder.DeathRecipient { - private final IMidiDeviceServer mServer; - private final MidiDeviceInfo mDeviceInfo; + private IMidiDeviceServer mServer; + private MidiDeviceInfo mDeviceInfo; + private final BluetoothDevice mBluetoothDevice; private MidiDeviceStatus mDeviceStatus; - private IBinder mDeviceStatusToken; + // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only) private final ServiceInfo mServiceInfo; // UID of device implementation private final int mUid; + // ServiceConnection for implementing Service (virtual devices only) + // mServiceConnection is non-null when connected or attempting to connect to the service + private ServiceConnection mServiceConnection; + + // List of all device connections for this device + private final ArrayList<DeviceConnection> mDeviceConnections + = new ArrayList<DeviceConnection>(); + public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo, ServiceInfo serviceInfo, int uid) { - mServer = server; mDeviceInfo = deviceInfo; mServiceInfo = serviceInfo; mUid = uid; + mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable( + MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);; + setDeviceServer(server); + } + + public Device(BluetoothDevice bluetoothDevice) { + mBluetoothDevice = bluetoothDevice; + mServiceInfo = null; + mUid = mBluetoothServiceUid; + } + + private void setDeviceServer(IMidiDeviceServer server) { + if (server != null) { + if (mServer != null) { + Log.e(TAG, "mServer already set in setDeviceServer"); + return; + } + IBinder binder = server.asBinder(); + try { + if (mDeviceInfo == null) { + mDeviceInfo = server.getDeviceInfo(); + } + binder.linkToDeath(this, 0); + mServer = server; + } catch (RemoteException e) { + mServer = null; + return; + } + mDevicesByServer.put(binder, this); + } else if (mServer != null) { + server = mServer; + mServer = null; + + IBinder binder = server.asBinder(); + mDevicesByServer.remove(binder); + + try { + server.closeDevice(); + binder.unlinkToDeath(this, 0); + } catch (RemoteException e) { + // nothing to do here + } + } + + if (mDeviceConnections != null) { + for (DeviceConnection connection : mDeviceConnections) { + connection.notifyClient(server); + } + } } public MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; } + // only used for bluetooth devices, which are created before we have a MidiDeviceInfo + public void setDeviceInfo(MidiDeviceInfo deviceInfo) { + mDeviceInfo = deviceInfo; + } + public MidiDeviceStatus getDeviceStatus() { return mDeviceStatus; } - public void setDeviceStatus(IBinder token, MidiDeviceStatus status) { + public void setDeviceStatus(MidiDeviceStatus status) { mDeviceStatus = status; - - if (mDeviceStatusToken == null && token != null) { - // register a death recipient so we can clear the status when the device dies - try { - token.linkToDeath(new IBinder.DeathRecipient() { - @Override - public void binderDied() { - // reset to default status and clear the token - mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); - mDeviceStatusToken = null; - notifyDeviceStatusChanged(Device.this, mDeviceStatus); - } - }, 0); - mDeviceStatusToken = token; - } catch (RemoteException e) { - // reset to default status - mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); - } - } } public IMidiDeviceServer getDeviceServer() { @@ -284,11 +382,102 @@ public class MidiService extends IMidiManager.Stub { return (!mDeviceInfo.isPrivate() || mUid == uid); } + public void addDeviceConnection(DeviceConnection connection) { + synchronized (mDeviceConnections) { + if (mServer != null) { + mDeviceConnections.add(connection); + connection.notifyClient(mServer); + } else if (mServiceConnection == null && + (mServiceInfo != null || mBluetoothDevice != null)) { + mDeviceConnections.add(connection); + + mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMidiDeviceServer server = IMidiDeviceServer.Stub.asInterface(service); + setDeviceServer(server); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + setDeviceServer(null); + mServiceConnection = null; + } + }; + + Intent intent; + if (mBluetoothDevice != null) { + intent = new Intent(MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT); + intent.setComponent(new ComponentName( + MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, + MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS)); + intent.putExtra("device", mBluetoothDevice); + } else { + intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); + intent.setComponent( + new ComponentName(mServiceInfo.packageName, mServiceInfo.name)); + } + + if (!mContext.bindService(intent, mServiceConnection, + Context.BIND_AUTO_CREATE)) { + Log.e(TAG, "Unable to bind service: " + intent); + setDeviceServer(null); + mServiceConnection = null; + } + } else { + Log.e(TAG, "No way to connect to device in addDeviceConnection"); + connection.notifyClient(null); + } + } + } + + public void removeDeviceConnection(DeviceConnection connection) { + synchronized (mDeviceConnections) { + mDeviceConnections.remove(connection); + + if (mDeviceConnections.size() == 0 && mServiceConnection != null) { + mContext.unbindService(mServiceConnection); + mServiceConnection = null; + if (mBluetoothDevice != null) { + // Bluetooth devices are ephemeral - remove when no clients exist + synchronized (mDevicesByInfo) { + closeLocked(); + } + } else { + setDeviceServer(null); + } + } + } + } + + // synchronize on mDevicesByInfo + public void closeLocked() { + synchronized (mDeviceConnections) { + for (DeviceConnection connection : mDeviceConnections) { + connection.getClient().removeDeviceConnection(connection); + } + mDeviceConnections.clear(); + } + setDeviceServer(null); + + // closed virtual devices should not be removed from mDevicesByInfo + // since they can be restarted on demand + if (mServiceInfo == null) { + removeDeviceLocked(this); + } else { + mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); + } + + if (mBluetoothDevice != null) { + mBluetoothDevices.remove(mBluetoothDevice); + } + } + + @Override public void binderDied() { + Log.d(TAG, "Device died: " + this); synchronized (mDevicesByInfo) { - if (mDevicesByInfo.remove(mDeviceInfo) != null) { - removeDeviceLocked(this); - } + closeLocked(); } } @@ -300,10 +489,56 @@ public class MidiService extends IMidiManager.Stub { sb.append(mDeviceStatus); sb.append(" UID: "); sb.append(mUid); + sb.append(" DeviceConnection count: "); + sb.append(mDeviceConnections.size()); + sb.append(" mServiceConnection: "); + sb.append(mServiceConnection); return sb.toString(); } } + // Represents a connection between a client and a device + private final class DeviceConnection { + private final IBinder mToken = new Binder(); + private final Device mDevice; + private final Client mClient; + private IMidiDeviceOpenCallback mCallback; + + public DeviceConnection(Device device, Client client, IMidiDeviceOpenCallback callback) { + mDevice = device; + mClient = client; + mCallback = callback; + } + + public Device getDevice() { + return mDevice; + } + + public Client getClient() { + return mClient; + } + + public IBinder getToken() { + return mToken; + } + + public void notifyClient(IMidiDeviceServer deviceServer) { + if (mCallback != null) { + try { + mCallback.onDeviceOpened(deviceServer, (deviceServer == null ? null : mToken)); + } catch (RemoteException e) { + // Client binderDied() method will do necessary cleanup, so nothing to do here + } + mCallback = null; + } + } + + @Override + public String toString() { + return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId(); + } + } + public MidiService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); @@ -321,6 +556,18 @@ public class MidiService extends IMidiManager.Stub { } } } + + PackageInfo info; + try { + info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0); + } catch (PackageManager.NameNotFoundException e) { + info = null; + } + if (info != null && info.applicationInfo != null) { + mBluetoothServiceUid = info.applicationInfo.uid; + } else { + mBluetoothServiceUid = -1; + } } @Override @@ -355,18 +602,61 @@ public class MidiService extends IMidiManager.Stub { } @Override - public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) { - Device device = mDevicesByInfo.get(deviceInfo); - if (device == null) { - Log.e(TAG, "device not found in openDevice: " + deviceInfo); - return null; + public void openDevice(IBinder token, MidiDeviceInfo deviceInfo, + IMidiDeviceOpenCallback callback) { + Client client = getClient(token); + if (client == null) return; + + Device device; + synchronized (mDevicesByInfo) { + device = mDevicesByInfo.get(deviceInfo); + if (device == null) { + throw new IllegalArgumentException("device does not exist: " + deviceInfo); + } + if (!device.isUidAllowed(Binder.getCallingUid())) { + throw new SecurityException("Attempt to open private device with wrong UID"); + } + } + + // clear calling identity so bindService does not fail + long identity = Binder.clearCallingIdentity(); + try { + client.addDeviceConnection(device, callback); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice, + IMidiDeviceOpenCallback callback) { + Client client = getClient(token); + if (client == null) return; + + // Bluetooth devices are created on demand + Device device; + synchronized (mDevicesByInfo) { + device = mBluetoothDevices.get(bluetoothDevice); + if (device == null) { + device = new Device(bluetoothDevice); + mBluetoothDevices.put(bluetoothDevice, device); + } } - if (!device.isUidAllowed(Binder.getCallingUid())) { - throw new SecurityException("Attempt to open private device with wrong UID"); + // clear calling identity so bindService does not fail + long identity = Binder.clearCallingIdentity(); + try { + client.addDeviceConnection(device, callback); + } finally { + Binder.restoreCallingIdentity(identity); } + } - return device.getDeviceServer(); + @Override + public void closeDevice(IBinder clientToken, IBinder deviceToken) { + Client client = getClient(clientToken); + if (client == null) return; + client.removeDeviceConnection(deviceToken); } @Override @@ -376,6 +666,8 @@ public class MidiService extends IMidiManager.Stub { int uid = Binder.getCallingUid(); if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) { throw new SecurityException("only system can create USB devices"); + } else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) { + throw new SecurityException("only MidiBluetoothService can create Bluetooth devices"); } synchronized (mDevicesByInfo) { @@ -389,8 +681,7 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDevicesByInfo) { Device device = mDevicesByServer.get(server.asBinder()); if (device != null) { - mDevicesByInfo.remove(device.getDeviceInfo()); - removeDeviceLocked(device); + device.closeLocked(); } } } @@ -420,19 +711,16 @@ public class MidiService extends IMidiManager.Stub { } @Override - public void setDeviceStatus(IBinder token, MidiDeviceStatus status) { - MidiDeviceInfo deviceInfo = status.getDeviceInfo(); - Device device = mDevicesByInfo.get(deviceInfo); - if (device == null) { - // Just return quietly here if device no longer exists - return; - } - if (Binder.getCallingUid() != device.getUid()) { - throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid() - + " does not match device's UID " + device.getUid()); + public void setDeviceStatus(IMidiDeviceServer server, MidiDeviceStatus status) { + Device device = mDevicesByServer.get(server.asBinder()); + if (device != null) { + if (Binder.getCallingUid() != device.getUid()) { + throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid() + + " does not match device's UID " + device.getUid()); + } + device.setDeviceStatus(status); + notifyDeviceStatusChanged(device, status); } - device.setDeviceStatus(token, status); - notifyDeviceStatusChanged(device, status); } private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) { @@ -452,18 +740,24 @@ public class MidiService extends IMidiManager.Stub { int id = mNextDeviceId++; MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, inputPortNames, outputPortNames, properties, isPrivate); - Device device = new Device(server, deviceInfo, serviceInfo, uid); - if (server != null) { - IBinder binder = server.asBinder(); - try { - binder.linkToDeath(device, 0); - } catch (RemoteException e) { - return null; + Device device = null; + BluetoothDevice bluetoothDevice = null; + if (type == MidiDeviceInfo.TYPE_BLUETOOTH) { + bluetoothDevice = (BluetoothDevice)properties.getParcelable( + MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE); + device = mBluetoothDevices.get(bluetoothDevice); + if (device != null) { + device.setDeviceInfo(deviceInfo); } - mDevicesByServer.put(binder, device); + } + if (device == null) { + device = new Device(server, deviceInfo, serviceInfo, uid); } mDevicesByInfo.put(deviceInfo, device); + if (bluetoothDevice != null) { + mBluetoothDevices.put(bluetoothDevice, device); + } synchronized (mClients) { for (Client c : mClients.values()) { @@ -478,8 +772,9 @@ public class MidiService extends IMidiManager.Stub { private void removeDeviceLocked(Device device) { IMidiDeviceServer server = device.getDeviceServer(); if (server != null) { - mDevicesByServer.remove(server); + mDevicesByServer.remove(server.asBinder()); } + mDevicesByInfo.remove(device.getDeviceInfo()); synchronized (mClients) { for (Client c : mClients.values()) { @@ -516,6 +811,15 @@ public class MidiService extends IMidiManager.Stub { MidiDeviceService.SERVICE_INTERFACE); if (parser == null) return; + // ignore virtual device servers that do not require the correct permission + if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals( + serviceInfo.permission)) { + Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName + + ": it does not require the permission " + + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE); + return; + } + Bundle properties = null; int numInputPorts = 0; int numOutputPorts = 0; |