diff --git a/media/java/android/media/midi/IMidiDeviceListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl index 17d9bfd9573c9..31c66e3f8250b 100644 --- a/media/java/android/media/midi/IMidiDeviceListener.aidl +++ b/media/java/android/media/midi/IMidiDeviceListener.aidl @@ -17,10 +17,12 @@ package android.media.midi; import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceStatus; /** @hide */ oneway interface IMidiDeviceListener { void onDeviceAdded(in MidiDeviceInfo device); void onDeviceRemoved(in MidiDeviceInfo device); + void onDeviceStatusChanged(in MidiDeviceStatus status); } diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index 617b03e704486..a3b40d68b73ea 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -19,6 +19,7 @@ package android.media.midi; import android.media.midi.IMidiDeviceListener; import android.media.midi.IMidiDeviceServer; import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceStatus; import android.os.Bundle; import android.os.IBinder; @@ -44,4 +45,11 @@ interface IMidiManager // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its // manifest's meta-data MidiDeviceInfo getServiceDeviceInfo(String packageName, String className); + + // used for client's to retrieve a device's MidiDeviceStatus + MidiDeviceStatus getDeviceStatus(in MidiDeviceInfo deviceInfo); + + // used by MIDI devices to report their status + // the token is used by MidiService for death notification + void setDeviceStatus(IBinder token, in MidiDeviceStatus status); } diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java index 6531052a56100..b3c0e3a485963 100644 --- a/media/java/android/media/midi/MidiDeviceServer.java +++ b/media/java/android/media/midi/MidiDeviceServer.java @@ -16,8 +16,8 @@ package android.media.midi; -import android.os.IBinder; import android.os.Binder; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; @@ -61,7 +61,25 @@ public final class MidiDeviceServer implements Closeable { private final CopyOnWriteArrayList mInputPorts = new CopyOnWriteArrayList(); + + // for reporting device status + private final IBinder mDeviceStatusToken = new Binder(); + private final boolean[] mInputPortBusy; + private final int[] mOutputPortOpenCount; + private final CloseGuard mGuard = CloseGuard.get(); + private boolean mIsClosed; + + private final Callback mCallback; + + public interface Callback { + /** + * Called to notify when an our device status has changed + * @param server the {@link MidiDeviceServer} that changed + * @param status the {@link MidiDeviceStatus} for the device + */ + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status); + } abstract private class PortClient implements IBinder.DeathRecipient { final IBinder mToken; @@ -96,7 +114,10 @@ public final class MidiDeviceServer implements Closeable { void close() { mToken.unlinkToDeath(this, 0); synchronized (mInputPortOutputPorts) { - mInputPortOutputPorts[mOutputPort.getPortNumber()] = null; + int portNumber = mOutputPort.getPortNumber(); + mInputPortOutputPorts[portNumber] = null; + mInputPortBusy[portNumber] = false; + updateDeviceStatus(); } IoUtils.closeQuietly(mOutputPort); } @@ -113,7 +134,15 @@ public final class MidiDeviceServer implements Closeable { @Override void close() { mToken.unlinkToDeath(this, 0); - mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort); + int portNumber = mInputPort.getPortNumber(); + MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber]; + synchronized (dispatcher) { + dispatcher.getSender().disconnect(mInputPort); + int openCount = dispatcher.getReceiverCount(); + mOutputPortOpenCount[portNumber] = openCount; + updateDeviceStatus(); + } + mInputPorts.remove(mInputPort); IoUtils.closeQuietly(mInputPort); } @@ -153,6 +182,8 @@ public final class MidiDeviceServer implements Closeable { synchronized (mPortClients) { mPortClients.put(token, client); } + mInputPortBusy[portNumber] = true; + updateDeviceStatus(); return pair[1]; } catch (IOException e) { Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); @@ -178,7 +209,14 @@ public final class MidiDeviceServer implements Closeable { ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( OsConstants.SOCK_SEQPACKET); MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber); - mOutputPortDispatchers[portNumber].getSender().connect(inputPort); + MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber]; + synchronized (dispatcher) { + dispatcher.getSender().connect(inputPort); + int openCount = dispatcher.getReceiverCount(); + mOutputPortOpenCount[portNumber] = openCount; + updateDeviceStatus(); + } + mInputPorts.add(inputPort); OutputPortClient client = new OutputPortClient(token, inputPort); synchronized (mPortClients) { @@ -215,11 +253,12 @@ public final class MidiDeviceServer implements Closeable { }; /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, - int numOutputPorts) { + int numOutputPorts, Callback callback) { mMidiManager = midiManager; mInputPortReceivers = inputPortReceivers; mInputPortCount = inputPortReceivers.length; mOutputPortCount = numOutputPorts; + mCallback = callback; mInputPortOutputPorts = new MidiOutputPort[mInputPortCount]; @@ -228,6 +267,9 @@ public final class MidiDeviceServer implements Closeable { mOutputPortDispatchers[i] = new MidiDispatcher(); } + mInputPortBusy = new boolean[mInputPortCount]; + mOutputPortOpenCount = new int[numOutputPorts]; + mGuard.open("close"); } @@ -242,9 +284,28 @@ public final class MidiDeviceServer implements Closeable { mDeviceInfo = deviceInfo; } + private void updateDeviceStatus() { + // clear calling identity, since we may be in a Binder call from one of our clients + long identityToken = Binder.clearCallingIdentity(); + + MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy, + mOutputPortOpenCount); + if (mCallback != null) { + mCallback.onDeviceStatusChanged(this, status); + } + try { + mMidiManager.setDeviceStatus(mDeviceStatusToken, status); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in updateDeviceStatus"); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + @Override public void close() throws IOException { synchronized (mGuard) { + if (mIsClosed) return; mGuard.close(); for (int i = 0; i < mInputPortCount; i++) { @@ -263,6 +324,7 @@ public final class MidiDeviceServer implements Closeable { } catch (RemoteException e) { Log.e(TAG, "RemoteException in unregisterDeviceServer"); } + mIsClosed = true; } } diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java index 64f69cdcd1320..5f55ae2963c15 100644 --- a/media/java/android/media/midi/MidiDeviceService.java +++ b/media/java/android/media/midi/MidiDeviceService.java @@ -57,6 +57,13 @@ abstract public class MidiDeviceService extends Service { private MidiDeviceServer mServer; private MidiDeviceInfo mDeviceInfo; + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + MidiDeviceService.this.onDeviceStatusChanged(status); + } + }; + @Override public void onCreate() { mMidiManager = IMidiManager.Stub.asInterface( @@ -75,7 +82,7 @@ abstract public class MidiDeviceService extends Service { inputPortReceivers = new MidiReceiver[0]; } server = new MidiDeviceServer(mMidiManager, inputPortReceivers, - deviceInfo.getOutputPortCount()); + deviceInfo.getOutputPortCount(), mCallback); server.setDeviceInfo(deviceInfo); } catch (RemoteException e) { Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo"); @@ -114,6 +121,13 @@ abstract public class MidiDeviceService extends Service { return mDeviceInfo; } + /** + * Called to notify when an our {@link MidiDeviceStatus} has changed + * @param status the number of the port that was opened + */ + public void onDeviceStatusChanged(MidiDeviceStatus status) { + } + @Override public IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/midi/MidiDeviceStatus.aidl b/media/java/android/media/midi/MidiDeviceStatus.aidl new file mode 100644 index 0000000000000..1a848c0686ad1 --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceStatus.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable MidiDeviceStatus; diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java new file mode 100644 index 0000000000000..cc048896d0a72 --- /dev/null +++ b/media/java/android/media/midi/MidiDeviceStatus.java @@ -0,0 +1,141 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +/** + * This is an immutable class that describes the current status of a MIDI device's ports. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public final class MidiDeviceStatus implements Parcelable { + + private static final String TAG = "MidiDeviceStatus"; + + private final MidiDeviceInfo mDeviceInfo; + // true if input ports are busy + private final boolean mInputPortBusy[]; + // open counts for output ports + private final int mOutputPortOpenCount[]; + + /** + * @hide + */ + public MidiDeviceStatus(MidiDeviceInfo deviceInfo, boolean inputPortBusy[], + int outputPortOpenCount[]) { + // MidiDeviceInfo is immutable so we can share references + mDeviceInfo = deviceInfo; + + // make copies of the arrays + mInputPortBusy = new boolean[inputPortBusy.length]; + System.arraycopy(inputPortBusy, 0, mInputPortBusy, 0, inputPortBusy.length); + mOutputPortOpenCount = new int[outputPortOpenCount.length]; + System.arraycopy(outputPortOpenCount, 0, mOutputPortOpenCount, 0, + outputPortOpenCount.length); + } + + /** + * Creates a MidiDeviceStatus with false for all input port busy values + * and zero for all output port open counts + * @hide + */ + public MidiDeviceStatus(MidiDeviceInfo deviceInfo) { + mDeviceInfo = deviceInfo; + mInputPortBusy = new boolean[deviceInfo.getInputPortCount()]; + mOutputPortOpenCount = new int[deviceInfo.getOutputPortCount()]; + } + + /** + * Returns the {@link MidiDeviceInfo} of the device. + * + * @return the device info + */ + public MidiDeviceInfo getDeviceInfo() { + return mDeviceInfo; + } + + /** + * Returns true if an input port is busy. + * + * @param input port's port number + * @return input port busy status + */ + public boolean isInputPortBusy(int portNumber) { + return mInputPortBusy[portNumber]; + } + + /** + * Returns the open count for an output port. + * + * @param output port's port number + * @return output port open count + */ + public int getOutputPortOpenCount(int portNumber) { + return mOutputPortOpenCount[portNumber]; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(mDeviceInfo.toString()); + int inputPortCount = mDeviceInfo.getInputPortCount(); + int outputPortCount = mDeviceInfo.getOutputPortCount(); + builder.append(" mInputPortBusy=["); + for (int i = 0; i < inputPortCount; i++) { + builder.append(mInputPortBusy[i]); + if (i < inputPortCount -1) { + builder.append(","); + } + } + builder.append("] mOutputPortOpenCount=["); + for (int i = 0; i < outputPortCount; i++) { + builder.append(mOutputPortOpenCount[i]); + if (i < outputPortCount -1) { + builder.append(","); + } + } + builder.append("]"); + return builder.toString(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public MidiDeviceStatus createFromParcel(Parcel in) { + ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader(); + MidiDeviceInfo deviceInfo = in.readParcelable(classLoader); + boolean[] inputPortBusy = in.createBooleanArray(); + int[] outputPortOpenCount = in.createIntArray(); + return new MidiDeviceStatus(deviceInfo, inputPortBusy, outputPortOpenCount); + } + + public MidiDeviceStatus[] newArray(int size) { + return new MidiDeviceStatus[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mDeviceInfo, flags); + parcel.writeBooleanArray(mInputPortBusy); + parcel.writeIntArray(mOutputPortOpenCount); + } +} diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java index 90789e583f454..d13ca74db3707 100644 --- a/media/java/android/media/midi/MidiDispatcher.java +++ b/media/java/android/media/midi/MidiDispatcher.java @@ -55,11 +55,11 @@ public final class MidiDispatcher extends MidiReceiver { }; /** - * Returns whether this dispatcher contains any receivers. - * @return true if the receiver list is not empty + * Returns the number of {@link MidiReceiver}s this dispatcher contains. + * @return the number of receivers */ - public boolean hasReceivers() { - return mReceivers.size() > 0; + public int getReceiverCount() { + return mReceivers.size(); } /** diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index d7b8c5702bd81..bab906419e25b 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -62,6 +62,7 @@ public final class MidiManager { mHandler = handler; } + @Override public void onDeviceAdded(MidiDeviceInfo device) { if (mHandler != null) { final MidiDeviceInfo deviceF = device; @@ -75,6 +76,7 @@ public final class MidiManager { } } + @Override public void onDeviceRemoved(MidiDeviceInfo device) { if (mHandler != null) { final MidiDeviceInfo deviceF = device; @@ -87,25 +89,49 @@ public final class MidiManager { mCallback.onDeviceRemoved(device); } } + + @Override + public void onDeviceStatusChanged(MidiDeviceStatus status) { + if (mHandler != null) { + final MidiDeviceStatus statusF = status; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceStatusChanged(statusF); + } + }); + } else { + mCallback.onDeviceStatusChanged(status); + } + } } /** * Callback class used for clients to receive MIDI device added and removed notifications */ - abstract public static class DeviceCallback { + 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 */ - abstract public void onDeviceAdded(MidiDeviceInfo 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 */ - abstract public void onDeviceRemoved(MidiDeviceInfo device); + public void onDeviceRemoved(MidiDeviceInfo device) { + } + + /** + * Called to notify when the status of a MIDI device has changed + * + * @param device a {@link MidiDeviceStatus} for the changed device + */ + public void onDeviceStatusChanged(MidiDeviceStatus status) { + } } /** @@ -251,10 +277,10 @@ public final class MidiManager { /** @hide */ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, - int numOutputPorts, Bundle properties, int type) { + int numOutputPorts, Bundle properties, int type, MidiDeviceServer.Callback callback) { try { MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, - numOutputPorts); + numOutputPorts, callback); MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), inputPortReceivers.length, numOutputPorts, properties, type); if (deviceInfo == null) { diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java index 7f98b300057e7..d534548d1dbc5 100644 --- a/services/core/java/com/android/server/MidiService.java +++ b/services/core/java/com/android/server/MidiService.java @@ -29,6 +29,7 @@ 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.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -147,6 +148,19 @@ public class MidiService extends IMidiManager.Stub { } } + public void deviceStatusChanged(Device device, MidiDeviceStatus status) { + // ignore private devices that our client cannot access + if (!device.isUidAllowed(mUid)) return; + + try { + for (IMidiDeviceListener listener : mListeners) { + listener.onDeviceStatusChanged(status); + } + } catch (RemoteException e) { + Log.e(TAG, "remote exception", e); + } + } + public void binderDied() { removeClient(mToken); } @@ -187,6 +201,8 @@ public class MidiService extends IMidiManager.Stub { private final class Device implements IBinder.DeathRecipient { private final IMidiDeviceServer mServer; private final MidiDeviceInfo mDeviceInfo; + private MidiDeviceStatus mDeviceStatus; + private IBinder mDeviceStatusToken; // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only) private final ServiceInfo mServiceInfo; // UID of device implementation @@ -204,6 +220,33 @@ public class MidiService extends IMidiManager.Stub { return mDeviceInfo; } + public MidiDeviceStatus getDeviceStatus() { + return mDeviceStatus; + } + + public void setDeviceStatus(IBinder token, 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() { return mServer; } @@ -216,6 +259,10 @@ public class MidiService extends IMidiManager.Stub { return (mServiceInfo == null ? null : mServiceInfo.packageName); } + public int getUid() { + return mUid; + } + public boolean isUidAllowed(int uid) { return (!mDeviceInfo.isPrivate() || mUid == uid); } @@ -302,13 +349,14 @@ public class MidiService extends IMidiManager.Stub { @Override public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts, int numOutputPorts, Bundle properties, int type) { - if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) { + int uid = Binder.getCallingUid(); + if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) { throw new SecurityException("only system can create non-virtual devices"); } synchronized (mDevicesByInfo) { return addDeviceLocked(type, numInputPorts, numOutputPorts, properties, - server, null, false, -1); + server, null, false, uid); } } @@ -337,6 +385,39 @@ public class MidiService extends IMidiManager.Stub { } } + @Override + public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) { + Device device = mDevicesByInfo.get(deviceInfo); + if (device == null) { + throw new IllegalArgumentException("no such device for " + deviceInfo); + } + return device.getDeviceStatus(); + } + + @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()); + } + device.setDeviceStatus(token, status); + notifyDeviceStatusChanged(device, status); + } + + private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) { + synchronized (mClients) { + for (Client c : mClients.values()) { + c.deviceStatusChanged(device, status); + } + } + } + // synchronize on mDevicesByInfo private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts, Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo, @@ -469,17 +550,15 @@ public class MidiService extends IMidiManager.Stub { continue; } - int uid = -1; - if (isPrivate) { - try { - ApplicationInfo appInfo = mPackageManager.getApplicationInfo( - serviceInfo.packageName, 0); - uid = appInfo.uid; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "could not fetch ApplicationInfo for " - + serviceInfo.packageName); - continue; - } + int uid; + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfo( + serviceInfo.packageName, 0); + uid = appInfo.uid; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "could not fetch ApplicationInfo for " + + serviceInfo.packageName); + continue; } synchronized (mDevicesByInfo) { diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index f927965af8863..725f39321eedf 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -121,7 +121,7 @@ public final class UsbMidiDevice implements Closeable { int outputCount = mOutputStreams.length; mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount, - properties, MidiDeviceInfo.TYPE_USB); + properties, MidiDeviceInfo.TYPE_USB, null); if (mServer == null) { return false; }