diff options
| author | 2011-07-21 15:10:10 -0700 | |
|---|---|---|
| committer | 2011-08-02 17:15:42 -0700 | |
| commit | 8f619182cb759718f64ab95fd6d61c16138f6952 (patch) | |
| tree | a6f3824563ebee8f80216f1ec5aafc4eb3d6f03b | |
| parent | 80df829e35d0a97f92e599d36b0b16dcc956130b (diff) | |
Remote control display API and implementation
Extend the media button event registration AudioManager API to
enable applications to register as a client of "remote controls"
and let them provide information meant to be displayed
by the remotes.
AudioService sends a AudioManager.REMOTE_CONTROL_CLIENT_CHANGED
intent to let remote controls know when / from whom they can
retrieve the information to display.
Only application that own audio focus, are the currently
registered media button event receiver, and have registered
a remote control client, are eligible to appear on the
remote control.
To address in future CLs:
- change how a remote control client forces a refresh
- rename methods called under lock to ___Locked()
- make API public
Change-Id: Icca30ab05dac2605ee9246f8acb27a03dcea077a
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 130 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 326 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 10 | ||||
| -rw-r--r-- | media/java/android/media/IRemoteControlClient.aidl | 76 |
5 files changed, 517 insertions, 26 deletions
diff --git a/Android.mk b/Android.mk index 5cb4d3054007..2558e093ed63 100644 --- a/Android.mk +++ b/Android.mk @@ -188,6 +188,7 @@ LOCAL_SRC_FILES += \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ + media/java/android/media/IRemoteControlClient.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 7258e115a7a7..731d1f38e75d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1646,7 +1646,8 @@ public class AudioManager { IAudioService service = getService(); try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, - mAudioFocusDispatcher, getIdForAudioFocusListener(l)); + mAudioFocusDispatcher, getIdForAudioFocusListener(l), + mContext.getPackageName() /* package name */); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() from AudioService due to "+e); } @@ -1682,7 +1683,9 @@ public class AudioManager { * in the application manifest. */ public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { - //TODO enforce the rule about the receiver being declared in the manifest + if (eventReceiver == null) { + return; + } IAudioService service = getService(); try { service.registerMediaButtonEventReceiver(eventReceiver); @@ -1697,6 +1700,9 @@ public class AudioManager { * that was registered with {@link #registerMediaButtonEventReceiver(ComponentName)}. */ public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { + if (eventReceiver == null) { + return; + } IAudioService service = getService(); try { service.unregisterMediaButtonEventReceiver(eventReceiver); @@ -1706,6 +1712,126 @@ public class AudioManager { } /** + * @hide + * Registers the remote control client for providing information to display on the remotes. + * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver} + * that will receive the media button intent, and associated with the remote control + * client. This method has no effect if + * {@link #registerMediaButtonEventReceiver(ComponentName)} hasn't been called + * with the same eventReceiver, or if + * {@link #unregisterMediaButtonEventReceiver(ComponentName)} has been called. + * @param rcClient the client associated with the event receiver, responsible for providing + * the information to display on the remote control. + */ + public void registerRemoteControlClient(ComponentName eventReceiver, + IRemoteControlClient rcClient) { + if (eventReceiver == null) { + return; + } + IAudioService service = getService(); + try { + service.registerRemoteControlClient(eventReceiver, rcClient, + // used to match media button event receiver and audio focus + mContext.getPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in registerRemoteControlClient"+e); + } + } + + /** + * @hide + * @param eventReceiver + */ + public void unregisterRemoteControlClient(ComponentName eventReceiver) { + if (eventReceiver == null) { + return; + } + IAudioService service = getService(); + try { + // unregistering a IRemoteControlClient is equivalent to setting it to null + service.registerRemoteControlClient(eventReceiver, null, mContext.getPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e); + } + } + + /** + * @hide + * Definitions of constants to be used in {@link android.media.IRemoteControlClient}. + */ + public final class RemoteControlParameters { + public final static int PLAYSTATE_STOPPED = 1; + public final static int PLAYSTATE_PAUSED = 2; + public final static int PLAYSTATE_PLAYING = 3; + public final static int PLAYSTATE_FAST_FORWARDING = 4; + public final static int PLAYSTATE_REWINDING = 5; + public final static int PLAYSTATE_SKIPPING_FORWARDS = 6; + public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7; + public final static int PLAYSTATE_BUFFERING = 8; + + public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; + public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; + public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; + public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; + public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; + public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; + public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; + public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; + } + + /** + * @hide + * Broadcast intent action indicating that the displays on the remote controls + * should be updated because a new remote control client is now active. If there is no + * {@link #EXTRA_REMOTE_CONTROL_CLIENT}, the remote control display should be cleared + * because there is no valid client to supply it with information. + * + * @see #EXTRA_REMOTE_CONTROL_CLIENT + */ + public static final String REMOTE_CONTROL_CLIENT_CHANGED = + "android.media.REMOTE_CONTROL_CLIENT_CHANGED"; + + /** + * @hide + * The IRemoteControlClient monotonically increasing generation counter. + * + * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION + */ + public static final String EXTRA_REMOTE_CONTROL_CLIENT = + "android.media.EXTRA_REMOTE_CONTROL_CLIENT"; + + /** + * @hide + * FIXME to be changed to address Neel's comments + * Force a refresh of the remote control client associated with the event receiver. + * @param eventReceiver + */ + public void refreshRemoteControlDisplay(ComponentName eventReceiver) { + IAudioService service = getService(); + try { + service.refreshRemoteControlDisplay(eventReceiver); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in refreshRemoteControlDisplay"+e); + } + } + + /** + * @hide + * FIXME API to be used by implementors of remote controls, not a candidate for SDK + */ + public void registerRemoteControlObserver() { + + } + + /** + * @hide + * FIXME API to be used by implementors of remote controls, not a candidate for SDK + */ + public void unregisterRemoteControlObserver() { + + } + + /** * @hide * Reload audio settings. This method is called by Settings backup * agent when audio settings are restored and causes the AudioService diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 682560a4cd3a..3e786c346dbb 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -55,6 +55,7 @@ import com.android.internal.telephony.ITelephony; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -113,6 +114,8 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_SET_FORCE_USE = 10; private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 11; private static final int MSG_BT_HEADSET_CNCT_FAILED = 12; + private static final int MSG_RCDISPLAY_CLEAR = 13; + private static final int MSG_RCDISPLAY_UPDATE = 14; private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; // Timeout for connection to bluetooth headset service @@ -371,7 +374,9 @@ public class AudioService extends IAudioService.Stub { // Register for media button intent broadcasts. intentFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON); - intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + // Workaround for bug on priority setting + //intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + intentFilter.setPriority(Integer.MAX_VALUE); context.registerReceiver(mMediaButtonReceiver, intentFilter); // Register for phone state monitoring @@ -885,7 +890,8 @@ public class AudioService extends IAudioService.Stub { requestAudioFocus(AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, cb, null /* IAudioFocusDispatcher allowed to be null only for this clientId */, - IN_VOICE_COMM_FOCUS_ID /*clientId*/); + IN_VOICE_COMM_FOCUS_ID /*clientId*/, + "system"); } } @@ -897,7 +903,8 @@ public class AudioService extends IAudioService.Stub { requestAudioFocus(AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, cb, null /* IAudioFocusDispatcher allowed to be null only for this clientId */, - IN_VOICE_COMM_FOCUS_ID /*clientId*/); + IN_VOICE_COMM_FOCUS_ID /*clientId*/, + "system"); } // if exiting call else if (newMode == AudioSystem.MODE_NORMAL) { @@ -2155,6 +2162,33 @@ public class AudioService extends IAudioService.Stub { persistMediaButtonReceiver( (ComponentName) msg.obj ); break; + case MSG_RCDISPLAY_CLEAR: + Log.i(TAG, "Clear remote control display"); + Intent clearIntent = new Intent(AudioManager.REMOTE_CONTROL_CLIENT_CHANGED); + // no extra means no IRemoteControlClient, which is a request to clear + clearIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcast(clearIntent); + break; + + case MSG_RCDISPLAY_UPDATE: + synchronized(mCurrentRcLock) { + if (mCurrentRcClientRef.get() == null) { + // the remote control display owner has changed between the + // the message to update the display was sent, and the time it + // gets to be processed (now) + } else { + mCurrentRcClientGen++; + Log.i(TAG, "Display/update remote control "); + Intent rcClientIntent = new Intent( + AudioManager.REMOTE_CONTROL_CLIENT_CHANGED); + rcClientIntent.putExtra(AudioManager.EXTRA_REMOTE_CONTROL_CLIENT, + mCurrentRcClientGen); + rcClientIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcast(rcClientIntent); + } + } + break; + case MSG_BT_HEADSET_CNCT_FAILED: resetBluetoothSco(); break; @@ -2567,23 +2601,25 @@ public class AudioService extends IAudioService.Stub { private static class FocusStackEntry { public int mStreamType = -1;// no stream type - public boolean mIsTransportControlReceiver = false; public IAudioFocusDispatcher mFocusDispatcher = null; public IBinder mSourceRef = null; public String mClientId; public int mFocusChangeType; + public String mPackageName; + public int mCallingUid; public FocusStackEntry() { } - public FocusStackEntry(int streamType, int duration, boolean isTransportControlReceiver, - IAudioFocusDispatcher afl, IBinder source, String id) { + public FocusStackEntry(int streamType, int duration, + IAudioFocusDispatcher afl, IBinder source, String id, String pn, int uid) { mStreamType = streamType; - mIsTransportControlReceiver = isTransportControlReceiver; mFocusDispatcher = afl; mSourceRef = source; mClientId = id; mFocusChangeType = duration; + mPackageName = pn; + mCallingUid = uid; } } @@ -2600,13 +2636,15 @@ public class AudioService extends IAudioService.Stub { while(stackIterator.hasNext()) { FocusStackEntry fse = stackIterator.next(); pw.println(" source:" + fse.mSourceRef + " -- client: " + fse.mClientId - + " -- duration: " +fse.mFocusChangeType); + + " -- duration: " + fse.mFocusChangeType + + " -- uid: " + fse.mCallingUid); } } } /** * Helper function: + * Called synchronized on mAudioFocusLock * Remove a focus listener from the focus stack. * @param focusListenerToRemove the focus listener * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding @@ -2621,6 +2659,10 @@ public class AudioService extends IAudioService.Stub { if (signal) { // notify the new top of the stack it gained focus notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay(); + } } } else { // focus is abandoned by a client that's not at the top of the stack, @@ -2639,6 +2681,7 @@ public class AudioService extends IAudioService.Stub { /** * Helper function: + * Called synchronized on mAudioFocusLock * Remove focus listeners from the focus stack for a particular client. */ private void removeFocusStackEntryForClient(IBinder cb) { @@ -2658,6 +2701,10 @@ public class AudioService extends IAudioService.Stub { // we removed an entry at the top of the stack: // notify the new top of the stack it gained focus. notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay(); + } } } @@ -2700,7 +2747,7 @@ public class AudioService extends IAudioService.Stub { /** @see AudioManager#requestAudioFocus(IAudioFocusDispatcher, int, int) */ public int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb, - IAudioFocusDispatcher fd, String clientId) { + IAudioFocusDispatcher fd, String clientId, String callingPackageName) { Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); // the main stream type for the audio focus request is currently not used. It may // potentially be used to handle multiple stream type-dependent audio focuses. @@ -2743,8 +2790,13 @@ public class AudioService extends IAudioService.Stub { removeFocusStackEntry(clientId, false); // push focus requester at the top of the audio focus stack - mFocusStack.push(new FocusStackEntry(mainStreamType, focusChangeHint, false, fd, cb, - clientId)); + mFocusStack.push(new FocusStackEntry(mainStreamType, focusChangeHint, fd, cb, + clientId, callingPackageName, Binder.getCallingUid())); + + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay(); + } }//synchronized(mAudioFocusLock) // handle the potential premature death of the new holder of the focus @@ -2831,19 +2883,100 @@ public class AudioService extends IAudioService.Stub { } } + private final static Object mCurrentRcLock = new Object(); + /** + * The one remote control client to be polled for display information. + * This object is never null, but its reference might. + * Access protected by mCurrentRcLock. + */ + private static SoftReference<IRemoteControlClient> mCurrentRcClientRef = + new SoftReference<IRemoteControlClient>(null); + + /** + * A monotonically increasing generation counter for mCurrentRcClientRef. + * Only accessed with a lock on mCurrentRcLock. + */ + private static int mCurrentRcClientGen = 0; + + /** + * Returns the current remote control client. + * @param rcClientId the counter value that matches the extra + * {@link AudioManager#EXTRA_REMOTE_CONTROL_CLIENT} in the + * {@link AudioManager#REMOTE_CONTROL_CLIENT_CHANGED} event + * @return the current IRemoteControlClient from which information to display on the remote + * control can be retrieved, or null if rcClientId doesn't match the current generation + * counter. + */ + public static IRemoteControlClient getRemoteControlClient(int rcClientId) { + synchronized(mCurrentRcLock) { + if (rcClientId == mCurrentRcClientGen) { + return mCurrentRcClientRef.get(); + } else { + return null; + } + } + } + + /** + * Inner class to monitor remote control client deaths, and remove the client for the + * remote control stack if necessary. + */ + private class RcClientDeathHandler implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private ComponentName mRcEventReceiver; + + RcClientDeathHandler(IBinder cb, ComponentName eventReceiver) { + mCb = cb; + mRcEventReceiver = eventReceiver; + } + + public void binderDied() { + Log.w(TAG, " RemoteControlClient died"); + // remote control client died, make sure the displays don't use it anymore + // by setting its remote control client to null + registerRemoteControlClient(mRcEventReceiver, null, null/*ignored*/); + } + + public IBinder getBinder() { + return mCb; + } + } + private static class RemoteControlStackEntry { + /** the target for the ACTION_MEDIA_BUTTON events */ public ComponentName mReceiverComponent;// always non null - // TODO implement registration expiration? - //public int mRegistrationTime; + public String mCallingPackageName; + public int mCallingUid; - public RemoteControlStackEntry() { - } + /** provides access to the information to display on the remote control */ + public SoftReference<IRemoteControlClient> mRcClientRef; + public RcClientDeathHandler mRcClientDeathHandler; public RemoteControlStackEntry(ComponentName r) { mReceiverComponent = r; + mCallingUid = -1; + mRcClientRef = new SoftReference<IRemoteControlClient>(null); + } + + public void unlinkToRcClientDeath() { + if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { + try { + mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); + } catch (java.util.NoSuchElementException e) { + // not much we can do here + Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()"); + e.printStackTrace(); + } + } } } + /** + * The stack of remote control event receivers. + * Code sections and methods that modify the remote control event receiver stack are + * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either + * stack, audio focus or RC, can lead to a change in the remote control display + */ private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); /** @@ -2855,8 +2988,10 @@ public class AudioService extends IAudioService.Stub { synchronized(mRCStack) { Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { - RemoteControlStackEntry fse = stackIterator.next(); - pw.println(" receiver:" + fse.mReceiverComponent); + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" receiver: " + rcse.mReceiverComponent + + " -- client: " + rcse.mRcClientRef.get() + + " -- uid: " + rcse.mCallingUid); } } } @@ -2909,6 +3044,7 @@ public class AudioService extends IAudioService.Stub { ComponentName receiverComponentName = ComponentName.unflattenFromString(receiverName); registerMediaButtonEventReceiver(receiverComponentName); } + // upon restoring (e.g. after boot), do we want to refresh all remotes? } /** @@ -2921,14 +3057,20 @@ public class AudioService extends IAudioService.Stub { return; } Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + RemoteControlStackEntry rcse = null; + boolean wasInsideStack = false; while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); + rcse = (RemoteControlStackEntry)stackIterator.next(); if(rcse.mReceiverComponent.equals(newReceiver)) { + wasInsideStack = true; stackIterator.remove(); break; } } - mRCStack.push(new RemoteControlStackEntry(newReceiver)); + if (!wasInsideStack) { + rcse = new RemoteControlStackEntry(newReceiver); + } + mRCStack.push(rcse); // post message to persist the default media button receiver mAudioHandler.sendMessage( mAudioHandler.obtainMessage( @@ -2950,13 +3092,88 @@ public class AudioService extends IAudioService.Stub { } } + /** + * Helper function: + * Called synchronized on mRCStack + */ + private boolean isCurrentRcController(ComponentName eventReceiver) { + if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(eventReceiver)) { + return true; + } + return false; + } + + /** + * Helper function: + * Called synchronized on mRCStack + */ + private void clearRemoteControlDisplay() { + synchronized(mCurrentRcLock) { + mCurrentRcClientRef.clear(); + } + mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); + } + + /** + * Helper function: + * Called synchronized on mRCStack + * mRCStack.empty() is false + */ + private void updateRemoteControlDisplay() { + RemoteControlStackEntry rcse = mRCStack.peek(); + // this is where we enforce opt-in for information display on the remote controls + // with the new AudioManager.registerRemoteControlClient() API + if (rcse.mRcClientRef.get() == null) { + // FIXME remove log before release: this warning will be displayed for every AF change + Log.w(TAG, "Can't update remote control display with null remote control client"); + clearRemoteControlDisplay(); + return; + } + synchronized(mCurrentRcLock) { + mCurrentRcClientRef = rcse.mRcClientRef; + } + mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, 0, 0, rcse) ); + } + + /** + * Helper function: + * Called synchronized on mFocusLock, then mRCStack + * Check whether the remote control display should be updated, triggers the update if required + */ + private void checkUpdateRemoteControlDisplay() { + // determine whether the remote control display should be refreshed + // if either stack is empty, there is a mismatch, so clear the RC display + if (mRCStack.isEmpty() || mFocusStack.isEmpty()) { + clearRemoteControlDisplay(); + return; + } + // if the top of the two stacks belong to different packages, there is a mismatch, clear + if ((mRCStack.peek().mCallingPackageName != null) + && (mFocusStack.peek().mPackageName != null) + && !(mRCStack.peek().mCallingPackageName.compareTo( + mFocusStack.peek().mPackageName) == 0)) { + clearRemoteControlDisplay(); + return; + } + // if the audio focus didn't originate from the same Uid as the one in which the remote + // control information will be retrieved, clear + if (mRCStack.peek().mCallingUid != mFocusStack.peek().mCallingUid) { + clearRemoteControlDisplay(); + return; + } + // refresh conditions were verified: update the remote controls + updateRemoteControlDisplay(); + } /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */ public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); - synchronized(mRCStack) { - pushMediaButtonReceiver(eventReceiver); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + pushMediaButtonReceiver(eventReceiver); + checkUpdateRemoteControlDisplay(); + } } } @@ -2964,11 +3181,74 @@ public class AudioService extends IAudioService.Stub { public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver); - synchronized(mRCStack) { - removeMediaButtonReceiver(eventReceiver); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + boolean topOfStackWillChange = isCurrentRcController(eventReceiver); + removeMediaButtonReceiver(eventReceiver); + if (topOfStackWillChange) { + checkUpdateRemoteControlDisplay(); + } + } } } + /** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */ + public void registerRemoteControlClient(ComponentName eventReceiver, + IRemoteControlClient rcClient, String callingPackageName) { + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // store the new display information + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mReceiverComponent.equals(eventReceiver)) { + // already had a remote control client? + if (rcse.mRcClientDeathHandler != null) { + // stop monitoring the old client's death + rcse.unlinkToRcClientDeath(); + } + // save the new remote control client + rcse.mRcClientRef = new SoftReference<IRemoteControlClient>(rcClient); + rcse.mCallingPackageName = callingPackageName; + rcse.mCallingUid = Binder.getCallingUid(); + if (rcClient == null) { + break; + } + // monitor the new client's death + IBinder b = rcClient.asBinder(); + RcClientDeathHandler rcdh = + new RcClientDeathHandler(b, rcse.mReceiverComponent); + try { + b.linkToDeath(rcdh, 0); + } catch (RemoteException e) { + // remote control client is DOA, disqualify it + Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); + rcse.mRcClientRef.clear(); + } + rcse.mRcClientDeathHandler = rcdh; + break; + } + } + // if the eventReceiver is at the top of the stack + // then check for potential refresh of the remote controls + if (isCurrentRcController(eventReceiver)) { + checkUpdateRemoteControlDisplay(); + } + } + } + } + + /** see AudioManager.refreshRemoteControlDisplay(ComponentName er) */ + public void refreshRemoteControlDisplay(ComponentName eventReceiver) { + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // only refresh if the eventReceiver is at the top of the stack + if (isCurrentRcController(eventReceiver)) { + checkUpdateRemoteControlDisplay(); + } + } + } + } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index e3bd7b4f8627..1a05f15248b7 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -18,6 +18,9 @@ package android.media; import android.content.ComponentName; import android.media.IAudioFocusDispatcher; +import android.media.IRemoteControlClient; +import android.net.Uri; +import android.os.Bundle; /** * {@hide} @@ -77,7 +80,7 @@ interface IAudioService { boolean isBluetoothScoOn(); int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, - String clientId); + String clientId, String callingPackageName); int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); @@ -87,6 +90,11 @@ interface IAudioService { void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); + void registerRemoteControlClient(in ComponentName eventReceiver, + in IRemoteControlClient rcClient, in String callingPackageName); + + void refreshRemoteControlDisplay(in ComponentName eventReceiver); + void startBluetoothSco(IBinder cb); void stopBluetoothSco(IBinder cb); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl new file mode 100644 index 000000000000..a49371c08668 --- /dev/null +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 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; + +import android.graphics.Bitmap; + +/** + * {@hide} + */ +interface IRemoteControlClient +{ + /** + * Called by a remote control to retrieve a String of information to display. + * @param field the identifier for a metadata field to retrieve. Valid values are + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. + * @return null if the given field is not supported, or the String matching the metadata field. + */ + String getMetadataString(int field); + + /** + * Returns the current playback state. + * @return one of the following values: + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_STOPPED}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_PAUSED}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_PLAYING}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_FAST_FORWARDING}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_REWINDING}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_SKIPPING_FORWARDS}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_SKIPPING_BACKWARDS}, + * {@link android.media.AudioManager.RemoteControl#PLAYSTATE_BUFFERING}. + */ + int getPlaybackState(); + + /** + * Returns the flags for the media transport control buttons this client supports. + * @see {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_PREVIOUS}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_REWIND}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_PLAY}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_PLAY_PAUSE}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_PAUSE}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_STOP}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_FAST_FORWARD}, + * {@link android.media.AudioManager.RemoteControl#FLAG_KEY_MEDIA_NEXT} + */ + int getTransportControlFlags(); + + Bitmap getAlbumArt(int width, int height); +} |