FileDocCategorySizeDatePackage
MediaFocusControl.javaAPI DocAndroid 5.1 API96438Thu Mar 12 22:22:30 GMT 2015android.media

MediaFocusControl

public class MediaFocusControl extends Object implements android.app.PendingIntent.OnFinished
hide

Fields Summary
private static final String
TAG
protected static final boolean
DEBUG_RC
Debug remote control client/display feature
protected static final boolean
DEBUG_VOL
Debug volumes
private boolean
mIsRinging
Used to alter media button redirection when the phone is ringing.
private final PowerManager.WakeLock
mMediaEventWakeLock
private final MediaEventHandler
mEventHandler
private final android.content.Context
mContext
private final android.content.ContentResolver
mContentResolver
private final AudioService.VolumeController
mVolumeController
private final android.app.AppOpsManager
mAppOps
private final android.app.KeyguardManager
mKeyguardManager
private final AudioService
mAudioService
private final NotificationListenerObserver
mNotifListenerObserver
private static final android.net.Uri
ENABLED_NOTIFICATION_LISTENERS_URI
private static final int
RCD_REG_FAILURE
private static final int
RCD_REG_SUCCESS_PERMISSION
private static final int
RCD_REG_SUCCESS_ENABLED_NOTIF
private static final int
MSG_RCDISPLAY_CLEAR
private static final int
MSG_RCDISPLAY_UPDATE
private static final int
MSG_REEVALUATE_REMOTE
private static final int
MSG_RCC_NEW_PLAYBACK_INFO
private static final int
MSG_RCC_NEW_VOLUME_OBS
private static final int
MSG_RCC_NEW_PLAYBACK_STATE
private static final int
MSG_RCC_SEEK_REQUEST
private static final int
MSG_RCC_UPDATE_METADATA
private static final int
MSG_RCDISPLAY_INIT_INFO
private static final int
MSG_REEVALUATE_RCD
private static final int
MSG_UNREGISTER_MEDIABUTTONINTENT
private static final int
SENDMSG_REPLACE
If the msg is already queued, replace it with this one.
private static final int
SENDMSG_NOOP
If the msg is already queued, ignore this one and leave the old.
private static final int
SENDMSG_QUEUE
If the msg is already queued, queue this one and leave the old.
protected static final String
IN_VOICE_COMM_FOCUS_ID
Constant to identify a focus stack entry that is used to hold the focus while the phone is ringing or during a call. Used by com.android.internal.telephony.CallManager when entering and exiting calls.
private static final Object
mAudioFocusLock
private static final Object
mRingingLock
private android.telephony.PhoneStateListener
mPhoneStateListener
private final Stack
mFocusStack
private boolean
mNotifyFocusOwnerOnDuck
Indicates whether to notify an audio focus owner when it loses focus with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck. This variable being false indicates an AudioPolicy has been registered and has signaled it will handle audio ducking.
private ArrayList
mFocusFollowers
private static final int
VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
The different actions performed in response to a voice button key event.
private static final int
VOICEBUTTON_ACTION_START_VOICE_INPUT
private static final int
VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS
private final Object
mVoiceEventLock
private boolean
mVoiceButtonDown
private boolean
mVoiceButtonHandled
private static final int
WAKELOCK_RELEASE_ON_FINISHED
private static final String
EXTRA_WAKELOCK_ACQUIRED
android.content.BroadcastReceiver
mKeyEventDone
private final Object
mCurrentRcLock
Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
private IRemoteControlClient
mCurrentRcClient
The one remote control client which will receive a request for display information. This object may be null. Access protected by mCurrentRcLock.
private android.app.PendingIntent
mCurrentRcClientIntent
The PendingIntent associated with mCurrentRcClient. Its value is irrelevant if mCurrentRcClient is null
private static final int
RC_INFO_NONE
private static final int
RC_INFO_ALL
private int
mCurrentRcClientGen
A monotonically increasing generation counter for mCurrentRcClient. Only accessed with a lock on mCurrentRcLock. No value wrap-around issues as we only act on equal values.
private android.media.PlayerRecord.RemotePlaybackState
mMainRemote
Internal cache for the playback information of the RemoteControlClient whose volume gets to be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack every time we need this info.
private boolean
mMainRemoteIsActive
Indicates whether the "main" RemoteControlClient is considered active. Use synchronized on mMainRemote.
private boolean
mHasRemotePlayback
Indicates whether there is remote playback going on. True even if there is no "active" remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it handles remote playback. Use synchronized on mMainRemote.
private final Stack
mPRStack
The stack of remote control event receivers. All read and write operations on mPRStack are synchronized.
private android.content.ComponentName
mMediaReceiverForCalls
The component the telephony package can register so telephony calls have priority to handle media button events
private ArrayList
mRcDisplays
The remote control displays. Access synchronized on mPRStack
Constructors Summary
protected MediaFocusControl(android.os.Looper looper, android.content.Context cntxt, AudioService.VolumeController volumeCtrl, AudioService as)


        
                
        mEventHandler = new MediaEventHandler(looper);
        mContext = cntxt;
        mContentResolver = mContext.getContentResolver();
        mVolumeController = volumeCtrl;
        mAudioService = as;

        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
        mMainRemote = new RemotePlaybackState(-1,
                AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC),
                AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC));

        // Register for phone state monitoring
        TelephonyManager tmgr = (TelephonyManager)
                mContext.getSystemService(Context.TELEPHONY_SERVICE);
        tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
        mKeyguardManager =
                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
        mNotifListenerObserver = new NotificationListenerObserver();

        mHasRemotePlayback = false;
        mMainRemoteIsActive = false;

        PlayerRecord.setMediaFocusControl(this);

        postReevaluateRemote();
    
Methods Summary
protected intabandonAudioFocus(IAudioFocusDispatcher fl, java.lang.String clientId, AudioAttributes aa)

see
AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)

        // AudioAttributes are currently ignored, to be used for zones
        Log.i(TAG, " AudioFocus  abandonAudioFocus() from " + clientId);
        try {
            // this will take care of notifying the new focus owner if needed
            synchronized(mAudioFocusLock) {
                removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
            }
        } catch (java.util.ConcurrentModificationException cme) {
            // Catching this exception here is temporary. It is here just to prevent
            // a crash seen when the "Silent" notification is played. This is believed to be fixed
            // but this try catch block is left just to be safe.
            Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
            cme.printStackTrace();
        }

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    
voidaddFocusFollower(android.media.audiopolicy.IAudioPolicyCallback ff)


       
        if (ff == null) {
            return;
        }
        synchronized(mAudioFocusLock) {
            boolean found = false;
            for (IAudioPolicyCallback pcb : mFocusFollowers) {
                if (pcb.asBinder().equals(ff.asBinder())) {
                    found = true;
                    break;
                }
            }
            if (found) {
                return;
            } else {
                mFocusFollowers.add(ff);
            }
        }
    
private booleancanReassignAudioFocus()
Helper function: Returns true if the system is in a state where the focus can be reevaluated, false otherwise. The implementation guarantees that a state where focus cannot be immediately reassigned implies that an "locked" focus owner is at the top of the focus stack. Modifications to the implementation that break this assumption will cause focus requests to misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.

        // focus requests are rejected during a phone call or when the phone is ringing
        // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
        if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
            return false;
        }
        return true;
    
private intcheckRcdRegistrationAuthorization(android.content.ComponentName listenerComp)
Checks a caller's authorization to register an IRemoteControlDisplay. Authorization is granted if one of the following is true:
  • the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission
  • the caller's listener is one of the enabled notification listeners

return
RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay registration.


                                                            
        
        // MEDIA_CONTENT_CONTROL permission check
        if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
            if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
            return RCD_REG_SUCCESS_PERMISSION;
        }

        // ENABLED_NOTIFICATION_LISTENERS settings check
        if (listenerComp != null) {
            // this call is coming from an app, can't use its identity to read secure settings
            final long ident = Binder.clearCallingIdentity();
            try {
                final int currentUser = ActivityManager.getCurrentUser();
                final String enabledNotifListeners = Settings.Secure.getStringForUser(
                        mContext.getContentResolver(),
                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                        currentUser);
                if (enabledNotifListeners != null) {
                    final String[] components = enabledNotifListeners.split(":");
                    for (int i=0; i<components.length; i++) {
                        final ComponentName component =
                                ComponentName.unflattenFromString(components[i]);
                        if (component != null) {
                            if (listenerComp.equals(component)) {
                                if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
                                        " is authorized notification listener"); }
                                return RCD_REG_SUCCESS_ENABLED_NOTIF;
                            }
                        }
                    }
                }
                if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
                        " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        return RCD_REG_FAILURE;
    
private voidcheckUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags)
Helper function: Called synchronized on mPRStack Check whether the remote control display should be updated, triggers the update if required

param
infoChangedFlags the flags corresponding to the remote control client information that has changed, if applicable (checking for the update conditions might trigger a clear, rather than an update event).

        // determine whether the remote control display should be refreshed
        // if the player record stack is empty, there is nothing to display, so clear the RC display
        if (mPRStack.isEmpty()) {
            clearRemoteControlDisplay_syncPrs();
            return;
        }

        // this is where more rules for refresh go

        // refresh conditions were verified: update the remote controls
        // ok to call: synchronized on mPRStack, mPRStack is not empty
        updateRemoteControlDisplay_syncPrs(infoChangedFlags);
    
protected booleancheckUpdateRemoteStateIfActive(int streamType)
Checks if a remote client is active on the supplied stream type. Update the remote stream volume state if found and playing

param
streamType
return
false if no remote playing is currently playing

        synchronized(mPRStack) {
            // iterating from top of stack as active playback is more likely on entries at the top
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
                            && isPlaystateActive(prse.mPlaybackState.mState)
                            && (prse.mPlaybackStream == streamType)) {
                        if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
                                + ", vol =" + prse.mPlaybackVolume);
                        synchronized (mMainRemote) {
                            mMainRemote.mRccId = prse.getRccId();
                            mMainRemote.mVolume = prse.mPlaybackVolume;
                            mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
                            mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
                            mMainRemoteIsActive = true;
                        }
                        return true;
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
            }
        }
        synchronized (mMainRemote) {
            mMainRemoteIsActive = false;
        }
        return false;
    
private voidclearRemoteControlDisplay_syncPrs()
Helper function: Called synchronized on mPRStack

        synchronized(mCurrentRcLock) {
            mCurrentRcClient = null;
        }
        // will cause onRcDisplayClear() to be called in AudioService's handler thread
        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
    
protected voiddiscardAudioFocusOwner()
Discard the current audio focus owner. Notify top of audio focus stack that it lost focus (regardless of possibility to reassign focus), remove it from the stack, and clear the remote control display.


                                          
       
        synchronized(mAudioFocusLock) {
            if (!mFocusStack.empty()) {
                // notify the current focus owner it lost focus after removing it from stack
                final FocusRequester exFocusOwner = mFocusStack.pop();
                exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
                exFocusOwner.release();
            }
        }
    
protected voiddispatchMediaKeyEvent(android.view.KeyEvent keyEvent)
No-op if the key code for keyEvent is not a valid media key (see {@link #isValidMediaKeyEvent(KeyEvent)})

param
keyEvent the key event to send

        filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
    
private voiddispatchMediaKeyEvent(android.view.KeyEvent keyEvent, boolean needWakeLock)
Handles the dispatching of the media button events to one of the registered listeners, or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.

param
keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
param
needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event is dispatched.

        if (needWakeLock) {
            mMediaEventWakeLock.acquire();
        }
        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
        synchronized(mPRStack) {
            if (!mPRStack.empty()) {
                // send the intent that was registered by the client
                try {
                    mPRStack.peek().getMediaButtonIntent().send(mContext,
                            needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
                            keyIntent, this, mEventHandler);
                } catch (CanceledException e) {
                    Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
                    e.printStackTrace();
                }
            } else {
                // legacy behavior when nobody registered their media button event receiver
                //    through AudioManager
                if (needWakeLock) {
                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
                }
                final long ident = Binder.clearCallingIdentity();
                try {
                    mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
                            null, mKeyEventDone,
                            mEventHandler, Activity.RESULT_OK, null, null);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
    
private voiddispatchMediaKeyEventForCalls(android.view.KeyEvent keyEvent, boolean needWakeLock)
Handles the dispatching of the media button events to the telephony package. Precondition: mMediaReceiverForCalls != null

param
keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
param
needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event is dispatched.

        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
        keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
        if (needWakeLock) {
            mMediaEventWakeLock.acquire();
            keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
        }
        final long ident = Binder.clearCallingIdentity();
        try {
            mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
                    null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    
protected voiddispatchMediaKeyEventUnderWakelock(android.view.KeyEvent keyEvent)
No-op if the key code for keyEvent is not a valid media key (see {@link #isValidMediaKeyEvent(KeyEvent)})

param
keyEvent the key event to send

        filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
    
protected voiddump(java.io.PrintWriter pw)

        dumpFocusStack(pw);
        dumpRCStack(pw);
        dumpRCCStack(pw);
        dumpRCDList(pw);
    
private voiddumpFocusStack(java.io.PrintWriter pw)
Helper function: Display in the log the current entries in the audio focus stack


                       
        
        pw.println("\nAudio Focus stack entries (last is top of stack):");
        synchronized(mAudioFocusLock) {
            Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
            while(stackIterator.hasNext()) {
                stackIterator.next().dump(pw);
            }
        }
        pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
    
private voiddumpRCCStack(java.io.PrintWriter pw)
Helper function: Display in the log the current entries in the remote control stack, focusing on RemoteControlClient data

        pw.println("\nRemote Control Client stack entries (last is top of stack):");
        synchronized(mPRStack) {
            Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
            while(stackIterator.hasNext()) {
                stackIterator.next().dump(pw, false);
            }
            synchronized(mCurrentRcLock) {
                pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
            }
        }
        synchronized (mMainRemote) {
            pw.println("\nRemote Volume State:");
            pw.println("  has remote: " + mHasRemotePlayback);
            pw.println("  is remote active: " + mMainRemoteIsActive);
            pw.println("  rccId: " + mMainRemote.mRccId);
            pw.println("  volume handling: "
                    + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
                            "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
            pw.println("  volume: " + mMainRemote.mVolume);
            pw.println("  volume steps: " + mMainRemote.mVolumeMax);
        }
    
private voiddumpRCDList(java.io.PrintWriter pw)
Helper function: Display in the log the current entries in the list of remote control displays

        pw.println("\nRemote Control Display list entries:");
        synchronized(mPRStack) {
            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
            while (displayIterator.hasNext()) {
                final DisplayInfoForServer di = displayIterator.next();
                pw.println("  IRCD: " + di.mRcDisplay +
                        "  -- w:" + di.mArtworkExpectedWidth +
                        "  -- h:" + di.mArtworkExpectedHeight +
                        "  -- wantsPosSync:" + di.mWantsPositionSync +
                        "  -- " + (di.mEnabled ? "enabled" : "disabled"));
            }
        }
    
private voiddumpRCStack(java.io.PrintWriter pw)
Helper function: Display in the log the current entries in the remote control focus stack


                        
        
        pw.println("\nRemote Control stack entries (last is top of stack):");
        synchronized(mPRStack) {
            Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
            while(stackIterator.hasNext()) {
                stackIterator.next().dump(pw, true);
            }
        }
    
private voidenableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, boolean enabled)

        // let all the remote control clients know whether the given display is enabled
        //   (so the remote control stack traversal order doesn't matter).
        final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
        while(stackIterator.hasNext()) {
            PlayerRecord prse = stackIterator.next();
            if(prse.getRcc() != null) {
                try {
                    prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error connecting RCD to client: ", e);
                }
            }
        }
    
private voidfilterMediaKeyEvent(android.view.KeyEvent keyEvent, boolean needWakeLock)

        // sanity check on the incoming key event
        if (!isValidMediaKeyEvent(keyEvent)) {
            Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
            return;
        }
        // event filtering for telephony
        synchronized(mRingingLock) {
            synchronized(mPRStack) {
                if ((mMediaReceiverForCalls != null) &&
                        (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
                    dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
                    return;
                }
            }
        }
        // event filtering based on voice-based interactions
        if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
            filterVoiceInputKeyEvent(keyEvent, needWakeLock);
        } else {
            dispatchMediaKeyEvent(keyEvent, needWakeLock);
        }
    
private voidfilterVoiceInputKeyEvent(android.view.KeyEvent keyEvent, boolean needWakeLock)
Filter key events that may be used for voice-based interactions

param
keyEvent a non-null KeyEvent whose key code is that of one of the supported media buttons that can be used to trigger voice-based interactions.
param
needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event is dispatched.


                                                                   
          
        if (DEBUG_RC) {
            Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
        }

        int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
        int keyAction = keyEvent.getAction();
        synchronized (mVoiceEventLock) {
            if (keyAction == KeyEvent.ACTION_DOWN) {
                if (keyEvent.getRepeatCount() == 0) {
                    // initial down
                    mVoiceButtonDown = true;
                    mVoiceButtonHandled = false;
                } else if (mVoiceButtonDown && !mVoiceButtonHandled
                        && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                    // long-press, start voice-based interactions
                    mVoiceButtonHandled = true;
                    voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
                }
            } else if (keyAction == KeyEvent.ACTION_UP) {
                if (mVoiceButtonDown) {
                    // voice button up
                    mVoiceButtonDown = false;
                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
                        voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
                    }
                }
            }
        }//synchronized (mVoiceEventLock)

        // take action after media button event filtering for voice-based interactions
        switch (voiceButtonAction) {
            case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
                if (DEBUG_RC) Log.v(TAG, "   ignore key event");
                break;
            case VOICEBUTTON_ACTION_START_VOICE_INPUT:
                if (DEBUG_RC) Log.v(TAG, "   start voice-based interactions");
                // then start the voice-based interactions
                startVoiceBasedInteractions(needWakeLock);
                break;
            case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
                if (DEBUG_RC) Log.v(TAG, "   send simulated key event, wakelock=" + needWakeLock);
                sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
                break;
        }
    
protected intgetCurrentAudioFocus()

        synchronized(mAudioFocusLock) {
            if (mFocusStack.empty()) {
                return AudioManager.AUDIOFOCUS_NONE;
            } else {
                return mFocusStack.peek().getGainRequest();
            }
        }
    
protected intgetRemoteStreamMaxVolume()

        synchronized (mMainRemote) {
            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
                return 0;
            }
            return mMainRemote.mVolumeMax;
        }
    
protected intgetRemoteStreamVolume()

        synchronized (mMainRemote) {
            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
                return 0;
            }
            return mMainRemote.mVolume;
        }
    
private booleanisComponentInStringArray(android.content.ComponentName comp, java.lang.String[] enabledArray)

param
comp a non-null ComponentName
param
enabledArray may be null
return

        if (enabledArray == null || enabledArray.length == 0) {
            if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
            return false;
        }
        final String compString = comp.flattenToString();
        for (int i=0; i<enabledArray.length; i++) {
            if (compString.equals(enabledArray[i])) {
                if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
                return true;
            }
        }
        if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
        return false;
    
private booleanisCurrentRcController(android.app.PendingIntent pi)
Helper function: Called synchronized on mPRStack

        if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
            return true;
        }
        return false;
    
private booleanisLockedFocusOwner(FocusRequester fr)

        return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
    
protected static booleanisPlaystateActive(int playState)
Returns true if the given playback state is considered "active", i.e. it describes a state where playback is happening, or about to

param
playState the playback state to evaluate
return
true if active, false otherwise (inactive or unknown)

        switch (playState) {
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
            case RemoteControlClient.PLAYSTATE_REWINDING:
            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
                return true;
            default:
                return false;
        }
    
private static booleanisValidMediaKeyEvent(android.view.KeyEvent keyEvent)

        if (keyEvent == null) {
            return false;
        }
        return KeyEvent.isMediaKey(keyEvent.getKeyCode());
    
private static booleanisValidVoiceInputKeyCode(int keyCode)
Checks whether the given key code is one that can trigger the launch of voice-based interactions.

param
keyCode the key code associated with the key event
return
true if the key is one of the supported voice-based interaction triggers

        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
            return true;
        } else {
            return false;
        }
    
booleanmustNotifyFocusOwnerOnDuck()

 return mNotifyFocusOwnerOnDuck; 
voidnotifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)
Called synchronized on mAudioFocusLock

        for (IAudioPolicyCallback pcb : mFocusFollowers) {
            try {
                // oneway
                pcb.notifyAudioFocusGrant(afi, requestResult);
            } catch (RemoteException e) {
                Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
                        + pcb.asBinder(), e);
            }
        }
    
voidnotifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)
Called synchronized on mAudioFocusLock

        for (IAudioPolicyCallback pcb : mFocusFollowers) {
            try {
                // oneway
                pcb.notifyAudioFocusLoss(afi, wasDispatched);
            } catch (RemoteException e) {
                Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
                        + pcb.asBinder(), e);
            }
        }
    
private voidnotifyTopOfAudioFocusStack()
Called synchronized on mAudioFocusLock

        // notify the top of the stack it gained focus
        if (!mFocusStack.empty()) {
            if (canReassignAudioFocus()) {
                mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
            }
        }
    
private voidonRcDisplayClear()
Called when processing MSG_RCDISPLAY_CLEAR event

        if (DEBUG_RC) Log.i(TAG, "Clear remote control display");

        synchronized(mPRStack) {
            synchronized(mCurrentRcLock) {
                mCurrentRcClientGen++;
                // synchronously update the displays and clients with the new client generation
                setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
                        null /*newMediaIntent*/, true /*clearing*/);
            }
        }
    
private voidonRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h)
Called when processing MSG_RCDISPLAY_INIT_INFO event Causes the current RemoteControlClient to send its info (metadata, playstate...) to a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.

        synchronized(mPRStack) {
            synchronized(mCurrentRcLock) {
                if (mCurrentRcClient != null) {
                    if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
                    try {
                        // synchronously update the new RCD with the current client generation
                        // and matching PendingIntent
                        newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
                                false);

                        // tell the current RCC that it needs to send info, but only to the new RCD
                        try {
                            mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Current valid remote client is dead: ", e);
                            mCurrentRcClient = null;
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
                    }
                }
            }
        }
    
private voidonRcDisplayUpdate(PlayerRecord prse, int flags)
Called when processing MSG_RCDISPLAY_UPDATE event

        synchronized(mPRStack) {
            synchronized(mCurrentRcLock) {
                if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
                    if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");

                    mCurrentRcClientGen++;
                    // synchronously update the displays and clients with
                    //      the new client generation
                    setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
                            prse.getMediaButtonIntent() /*newMediaIntent*/,
                            false /*clearing*/);

                    // tell the current client that it needs to send info
                    try {
                        //TODO change name to informationRequestForAllDisplays()
                        mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Current valid remote client is dead: "+e);
                        mCurrentRcClient = null;
                    }
                } else {
                    // 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)
                }
            }
        }
    
private voidonReevaluateRemote()

        // TODO This was used to notify VolumePanel if there was remote playback
        // in the stack. This is now in MediaSessionService. More code should be
        // removed.
    
private voidonReevaluateRemoteControlDisplays()

        if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
        // read which components are enabled notification listeners
        final int currentUser = ActivityManager.getCurrentUser();
        final String enabledNotifListeners = Settings.Secure.getStringForUser(
                mContext.getContentResolver(),
                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                currentUser);
        if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
        synchronized(mAudioFocusLock) {
            synchronized(mPRStack) {
                // check whether the "enable" status of each RCD with a notification listener
                // has changed
                final String[] enabledComponents;
                if (enabledNotifListeners == null) {
                    enabledComponents = null;
                } else {
                    enabledComponents = enabledNotifListeners.split(":");
                }
                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
                while (displayIterator.hasNext()) {
                    final DisplayInfoForServer di =
                            displayIterator.next();
                    if (di.mClientNotifListComp != null) {
                        boolean wasEnabled = di.mEnabled;
                        di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
                                enabledComponents);
                        if (wasEnabled != di.mEnabled){
                            try {
                                // tell the RCD whether it's enabled
                                di.mRcDisplay.setEnabled(di.mEnabled);
                                // tell the RCCs about the change for this RCD
                                enableRemoteControlDisplayForClient_syncRcStack(
                                        di.mRcDisplay, di.mEnabled);
                                // when enabling, refresh the information on the display
                                if (di.mEnabled) {
                                    sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
                                            di.mArtworkExpectedWidth /*arg1*/,
                                            di.mArtworkExpectedHeight/*arg2*/,
                                            di.mRcDisplay /*obj*/, 0/*delay*/);
                                }
                            } catch (RemoteException e) {
                                Log.e(TAG, "Error en/disabling RCD: ", e);
                            }
                        }
                    }
                }
            }
        }
    
private voidonRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo)

        synchronized(mPRStack) {
            // The stack traversal order doesn't matter because there is only one stack entry
            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
            //  start iterating from the top.
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    if (prse.getRccId() == rccId) {
                        prse.mRemoteVolumeObs = rvo;
                        break;
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
            }
        }
    
public voidonSendFinished(android.app.PendingIntent pendingIntent, android.content.Intent intent, int resultCode, java.lang.String resultData, android.os.Bundle resultExtras)


         
                  
        if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
            mMediaEventWakeLock.release();
        }
    
private voidplugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc)
Plug each registered display into the specified client

param
rcc, guaranteed non null


                      
        
        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
        while (displayIterator.hasNext()) {
            final DisplayInfoForServer di = displayIterator.next();
            try {
                rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
                        di.mArtworkExpectedHeight);
                if (di.mWantsPositionSync) {
                    rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
            }
        }
    
protected voidpostReevaluateRemote()
Call to make AudioService reevaluate whether it's in a mode where remote players should have their volume controlled. In this implementation this is only to reset whether VolumePanel should display remote volumes

        sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
    
private voidpostReevaluateRemoteControlDisplays()

        sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
    
private voidpropagateFocusLossFromGain_syncAf(int focusGain)
Focus is requested, propagate the associated loss throughout the stack.

param
focusGain the new focus gain that will later be added at the top of the stack

        // going through the audio focus stack to signal new focus, traversing order doesn't
        // matter as all entries respond to the same external focus gain
        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
        while(stackIterator.hasNext()) {
            stackIterator.next().handleExternalFocusGain(focusGain);
        }
    
private intpushBelowLockedFocusOwners(FocusRequester nfr)
Helper function Pre-conditions: focus stack is not empty, there is one or more locked focus owner at the top of the focus stack Push the focus requester onto the audio focus stack at the first position immediately following the locked focus owners.

return
{@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}

        int lastLockedFocusOwnerIndex = mFocusStack.size();
        for (int index = mFocusStack.size()-1; index >= 0; index--) {
            if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                lastLockedFocusOwnerIndex = index;
            }
        }
        if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
            // this should not happen, but handle it and log an error
            Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                    new Exception());
            // no exclusive owner, push at top of stack, focus is granted, propagate change
            propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
            mFocusStack.push(nfr);
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
        }
    
private booleanpushMediaButtonReceiver_syncPrs(android.app.PendingIntent mediaIntent, android.content.ComponentName target, android.os.IBinder token)
Helper function: Push the new media button receiver "near" the top of the PlayerRecord stack. "Near the top" is defined as: - at the top if the current PlayerRecord at the top is not playing - below the entries at the top of the stack that correspond to the playing PlayerRecord otherwise Called synchronized on mPRStack precondition: mediaIntent != null

return
true if the top of mPRStack was changed, false otherwise

        if (mPRStack.empty()) {
            mPRStack.push(new PlayerRecord(mediaIntent, target, token));
            return true;
        } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
            // already at top of stack
            return false;
        }
        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
                mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
            return false;
        }
        PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
        boolean topChanged = false;
        PlayerRecord prse = null;
        int lastPlayingIndex = mPRStack.size();
        int inStackIndex = -1;
        try {
            // go through the stack from the top to figure out who's playing, and the position
            // of this media button receiver (note that it may not be in the stack)
            for (int index = mPRStack.size()-1; index >= 0; index--) {
                prse = mPRStack.elementAt(index);
                if (prse.isPlaybackActive()) {
                    lastPlayingIndex = index;
                }
                if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
                    inStackIndex = index;
                }
            }

            if (inStackIndex == -1) {
                // is not in stack
                prse = new PlayerRecord(mediaIntent, target, token);
                // it's new so it's not playing (no RemoteControlClient to give a playstate),
                // therefore it goes after the ones with active playback
                mPRStack.add(lastPlayingIndex, prse);
            } else {
                // is in the stack
                if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
                    prse = mPRStack.elementAt(inStackIndex);
                    // remove it from its old location in the stack
                    mPRStack.removeElementAt(inStackIndex);
                    if (prse.isPlaybackActive()) {
                        // and put it at the top
                        mPRStack.push(prse);
                    } else {
                        // and put it after the ones with active playback
                        if (inStackIndex > lastPlayingIndex) {
                            mPRStack.add(lastPlayingIndex, prse);
                        } else {
                            mPRStack.add(lastPlayingIndex - 1, prse);
                        }
                    }
                }
            }

        } catch (ArrayIndexOutOfBoundsException e) {
            // not expected to happen, indicates improper concurrent modification or bad index
            Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
                    + " size=" + mPRStack.size()
                    + " accessing media button stack", e);
        }

        return (topChanged);
    
private booleanrcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd)
Is the remote control display interface already registered

param
rcd
return
true if the IRemoteControlDisplay is already in the list of displays

        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
        while (displayIterator.hasNext()) {
            final DisplayInfoForServer di = displayIterator.next();
            if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                return true;
            }
        }
        return false;
    
protected voidregisterMediaButtonEventReceiverForCalls(android.content.ComponentName c)
see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) precondition: c != null

        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
                != PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG, "Invalid permissions to register media button receiver for calls");
            return;
        }
        synchronized(mPRStack) {
            mMediaReceiverForCalls = c;
        }
    
protected voidregisterMediaButtonIntent(android.app.PendingIntent mediaIntent, android.content.ComponentName eventReceiver, android.os.IBinder token)
see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) precondition: mediaIntent != null

        Log.i(TAG, "  Remote Control   registerMediaButtonIntent() for " + mediaIntent);

        synchronized(mPRStack) {
            if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
                // new RC client, assume every type of information shall be queried
                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
            }
        }
    
protected intregisterRemoteControlClient(android.app.PendingIntent mediaIntent, IRemoteControlClient rcClient, java.lang.String callingPackageName)
see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)

return
the unique ID of the PlayerRecord associated with the RemoteControlClient Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient without modifying the RC stack, but while still causing the display to refresh (will become blank as a result of this)

        if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
        synchronized(mPRStack) {
            // store the new display information
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
                        prse.resetControllerInfoForRcc(rcClient, callingPackageName,
                                Binder.getCallingUid());

                        if (rcClient == null) {
                            break;
                        }

                        rccId = prse.getRccId();

                        // there is a new (non-null) client:
                        //     give the new client the displays (if any)
                        if (mRcDisplays.size() > 0) {
                            plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
                        }
                        break;
                    }
                }//for
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
            }

            // if the eventReceiver is at the top of the stack
            // then check for potential refresh of the remote controls
            if (isCurrentRcController(mediaIntent)) {
                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
            }
        }//synchronized(mPRStack)
        return rccId;
    
protected booleanregisterRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h)

        int reg = checkRcdRegistrationAuthorization(null);
        if (reg != RCD_REG_FAILURE) {
            registerRemoteControlDisplay_int(rcd, w, h, null);
            return true;
        } else {
            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
                    " to register IRemoteControlDisplay");
            return false;
        }
    
private voidregisterRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, android.content.ComponentName listenerComp)
Register an IRemoteControlDisplay. Notify all IRemoteControlClient of the new display and cause the RemoteControlClient at the top of the stack to update the new display with its information.

see
android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
param
rcd the IRemoteControlDisplay to register. No effect if null.
param
w the maximum width of the expected bitmap. Negative or zero values indicate this display doesn't need to receive artwork.
param
h the maximum height of the expected bitmap. Negative or zero values indicate this display doesn't need to receive artwork.
param
listenerComp the component for the listener interface, may be null if it's not needed to verify it belongs to one of the enabled notification listeners

        if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
        synchronized(mAudioFocusLock) {
            synchronized(mPRStack) {
                if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
                    return;
                }
                DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
                di.mEnabled = true;
                di.mClientNotifListComp = listenerComp;
                if (!di.init()) {
                    if (DEBUG_RC) Log.e(TAG, " error registering RCD");
                    return;
                }
                // add RCD to list of displays
                mRcDisplays.add(di);

                // let all the remote control clients know there is a new display (so the remote
                //   control stack traversal order doesn't matter).
                Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
                while(stackIterator.hasNext()) {
                    PlayerRecord prse = stackIterator.next();
                    if(prse.getRcc() != null) {
                        try {
                            prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error connecting RCD to client: ", e);
                        }
                    }
                }

                // we have a new display, of which all the clients are now aware: have it be
                // initialized wih the current gen ID and the current client info, do not
                // reset the information for the other (existing) displays
                sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
                        w /*arg1*/, h /*arg2*/,
                        rcd /*obj*/, 0/*delay*/);
            }
        }
    
protected booleanregisterRemoteController(IRemoteControlDisplay rcd, int w, int h, android.content.ComponentName listenerComp)

        int reg = checkRcdRegistrationAuthorization(listenerComp);
        if (reg != RCD_REG_FAILURE) {
            registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
            return true;
        } else {
            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
                    " or be an enabled NotificationListenerService for registerRemoteController");
            return false;
        }
    
protected voidremoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h)
Update the size of the artwork used by an IRemoteControlDisplay.

see
android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
param
rcd the IRemoteControlDisplay with the new artwork size requirement
param
w the maximum width of the expected bitmap. Negative or zero values indicate this display doesn't need to receive artwork.
param
h the maximum height of the expected bitmap. Negative or zero values indicate this display doesn't need to receive artwork.

        synchronized(mPRStack) {
            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
            boolean artworkSizeUpdate = false;
            while (displayIterator.hasNext() && !artworkSizeUpdate) {
                final DisplayInfoForServer di = displayIterator.next();
                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
                        di.mArtworkExpectedWidth = w;
                        di.mArtworkExpectedHeight = h;
                        artworkSizeUpdate = true;
                    }
                }
            }
            if (artworkSizeUpdate) {
                // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
                // stack traversal order doesn't matter
                final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
                while(stackIterator.hasNext()) {
                    final PlayerRecord prse = stackIterator.next();
                    if(prse.getRcc() != null) {
                        try {
                            prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
                        }
                    }
                }
            }
        }
    
protected voidremoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, boolean wantsSync)
Controls whether a remote control display needs periodic checks of the RemoteControlClient playback position to verify that the estimated position has not drifted from the actual position. By default the check is not performed. The IRemoteControlDisplay must have been previously registered for this to have any effect.

param
rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled or disabled. Not null.
param
wantsSync if true, RemoteControlClient instances which expose their playback position to the framework will regularly compare the estimated playback position with the actual position, and will update the IRemoteControlDisplay implementation whenever a drift is detected.

        synchronized(mPRStack) {
            boolean rcdRegistered = false;
            // store the information about this display
            // (display stack traversal order doesn't matter).
            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
            while (displayIterator.hasNext()) {
                final DisplayInfoForServer di = displayIterator.next();
                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                    di.mWantsPositionSync = wantsSync;
                    rcdRegistered = true;
                    break;
                }
            }
            if (!rcdRegistered) {
                return;
            }
            // notify all current RemoteControlClients
            // (stack traversal order doesn't matter as we notify all RCCs)
            final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
            while (stackIterator.hasNext()) {
                final PlayerRecord prse = stackIterator.next();
                if (prse.getRcc() != null) {
                    try {
                        prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
                    }
                }
            }
        }
    
voidremoveFocusFollower(android.media.audiopolicy.IAudioPolicyCallback ff)

        if (ff == null) {
            return;
        }
        synchronized(mAudioFocusLock) {
            for (IAudioPolicyCallback pcb : mFocusFollowers) {
                if (pcb.asBinder().equals(ff.asBinder())) {
                    mFocusFollowers.remove(pcb);
                    break;
                }
            }
        }
    
private voidremoveFocusStackEntry(java.lang.String clientToRemove, boolean signal, boolean notifyFocusFollowers)
Helper function: Called synchronized on mAudioFocusLock Remove a focus listener from the focus stack.

param
clientToRemove the focus listener
param
signal if true and the listener was at the top of the focus stack, i.e. it was holding focus, notify the next item in the stack it gained focus.

        // is the current top of the focus stack abandoning focus? (because of request, not death)
        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
        {
            //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
            FocusRequester fr = mFocusStack.pop();
            fr.release();
            if (notifyFocusFollowers) {
                final AudioFocusInfo afi = fr.toAudioFocusInfo();
                afi.clearLossReceived();
                notifyExtPolicyFocusLoss_syncAf(afi, false);
            }
            if (signal) {
                // notify the new top of the stack it gained focus
                notifyTopOfAudioFocusStack();
            }
        } else {
            // focus is abandoned by a client that's not at the top of the stack,
            // no need to update focus.
            // (using an iterator on the stack so we can safely remove an entry after having
            //  evaluated it, traversal order doesn't matter here)
            Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
            while(stackIterator.hasNext()) {
                FocusRequester fr = stackIterator.next();
                if(fr.hasSameClient(clientToRemove)) {
                    Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
                            + clientToRemove);
                    stackIterator.remove();
                    fr.release();
                }
            }
        }
    
private voidremoveFocusStackEntryForClient(android.os.IBinder cb)
Helper function: Called synchronized on mAudioFocusLock Remove focus listeners from the focus stack for a particular client when it has died.

        // is the owner of the audio focus part of the client to remove?
        boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
                mFocusStack.peek().hasSameBinder(cb);
        // (using an iterator on the stack so we can safely remove an entry after having
        //  evaluated it, traversal order doesn't matter here)
        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
        while(stackIterator.hasNext()) {
            FocusRequester fr = stackIterator.next();
            if(fr.hasSameBinder(cb)) {
                Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for " + cb);
                stackIterator.remove();
                // the client just died, no need to unlink to its death
            }
        }
        if (isTopOfStackForClientToRemove) {
            // we removed an entry at the top of the stack:
            //  notify the new top of the stack it gained focus.
            notifyTopOfAudioFocusStack();
        }
    
private voidremoveMediaButtonReceiver_syncPrs(android.app.PendingIntent pi)
Helper function: Remove the remote control receiver from the RC focus stack. Called synchronized on mPRStack precondition: pi != null

        try {
            for (int index = mPRStack.size()-1; index >= 0; index--) {
                final PlayerRecord prse = mPRStack.elementAt(index);
                if (prse.hasMatchingMediaButtonIntent(pi)) {
                    prse.destroy();
                    // ok to remove element while traversing the stack since we're leaving the loop
                    mPRStack.removeElementAt(index);
                    break;
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            // not expected to happen, indicates improper concurrent modification
            Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
        }
    
protected intrequestAudioFocus(AudioAttributes aa, int focusChangeHint, android.os.IBinder cb, IAudioFocusDispatcher fd, java.lang.String clientId, java.lang.String callingPackageName, int flags)

see
AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int)

        Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
                "flags=0x" + Integer.toHexString(flags));
        // we need a valid binder callback for clients
        if (!cb.pingBinder()) {
            Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
        }

        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
                callingPackageName) != AppOpsManager.MODE_ALLOWED) {
            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
        }

        synchronized(mAudioFocusLock) {
            boolean focusGrantDelayed = false;
            if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                    // granted right now, so the requester will be inserted in the focus stack
                    // to receive focus later
                    focusGrantDelayed = true;
                }
            }

            // handle the potential premature death of the new holder of the focus
            // (premature death == death before abandoning focus)
            // Register for client death notification
            AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
            try {
                cb.linkToDeath(afdh, 0);
            } catch (RemoteException e) {
                // client has already died!
                Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }

            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                // if focus is already owned by this client and the reason for acquiring the focus
                // hasn't changed, don't do anything
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    // unlink death handler so it can be gc'ed.
                    // linkToDeath() creates a JNI global reference preventing collection.
                    cb.unlinkToDeath(afdh, 0);
                    notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
                // the reason for the audio focus request has changed: remove the current top of
                // stack and respond as if we had a new focus owner
                if (!focusGrantDelayed) {
                    mFocusStack.pop();
                    // the entry that was "popped" is the same that was "peeked" above
                    fr.release();
                }
            }

            // focus requester might already be somewhere below in the stack, remove it
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);

            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
            if (focusGrantDelayed) {
                // focusGrantDelayed being true implies we can't reassign focus right now
                // which implies the focus stack is not empty.
                final int requestResult = pushBelowLockedFocusOwners(nfr);
                if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                }
                return requestResult;
            } else {
                // propagate the focus change through the stack
                if (!mFocusStack.empty()) {
                    propagateFocusLossFromGain_syncAf(focusChangeHint);
                }

                // push focus requester at the top of the audio focus stack
                mFocusStack.push(nfr);
            }
            notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);

        }//synchronized(mAudioFocusLock)

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    
private static voidsendMsg(android.os.Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, java.lang.Object obj, int delay)


          
                      

        if (existingMsgPolicy == SENDMSG_REPLACE) {
            handler.removeMessages(msg);
        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
            return;
        }

        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
    
private voidsendSimulatedMediaButtonEvent(android.view.KeyEvent originalKeyEvent, boolean needWakeLock)

        // send DOWN event
        KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
        dispatchMediaKeyEvent(keyEvent, needWakeLock);
        // send UP event
        keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
        dispatchMediaKeyEvent(keyEvent, needWakeLock);

    
private voidsendVolumeUpdateToRemote(int rccId, int direction)

        if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
        if (direction == 0) {
            // only handling discrete events
            return;
        }
        IRemoteVolumeObserver rvo = null;
        synchronized (mPRStack) {
            // The stack traversal order doesn't matter because there is only one stack entry
            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
            //  start iterating from the top.
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
                    if (prse.getRccId() == rccId) {
                        rvo = prse.mRemoteVolumeObs;
                        break;
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
            }
        }
        if (rvo != null) {
            try {
                rvo.dispatchRemoteVolumeUpdate(direction, -1);
            } catch (RemoteException e) {
                Log.e(TAG, "Error dispatching relative volume update", e);
            }
        }
    
protected voidsetDuckingInExtPolicyAvailable(boolean available)


        
        mNotifyFocusOwnerOnDuck = !available;
    
private voidsetNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration)
Update the remote control clients with the new "focused" client generation

        // (using an iterator on the stack so we can safely remove an entry if needed,
        //  traversal order doesn't matter here as we update all entries)
        Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
        while(stackIterator.hasNext()) {
            PlayerRecord se = stackIterator.next();
            if ((se != null) && (se.getRcc() != null)) {
                try {
                    se.getRcc().setCurrentClientGenerationId(newClientGeneration);
                } catch (RemoteException e) {
                    Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
                    stackIterator.remove();
                    se.unlinkToRcClientDeath();
                }
            }
        }
    
private voidsetNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, android.app.PendingIntent newMediaIntent, boolean clearing)
Update the remote control displays with the new "focused" client generation

        synchronized(mPRStack) {
            if (mRcDisplays.size() > 0) {
                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
                while (displayIterator.hasNext()) {
                    final DisplayInfoForServer di = displayIterator.next();
                    try {
                        di.mRcDisplay.setCurrentClientId(
                                newClientGeneration, newMediaIntent, clearing);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
                        di.release();
                        displayIterator.remove();
                    }
                }
            }
        }
    
private voidsetNewRcClient_syncRcsCurrc(int newClientGeneration, android.app.PendingIntent newMediaIntent, boolean clearing)
Update the displays and clients with the new "focused" client generation and name

param
newClientGeneration the new generation value matching a client update
param
newMediaIntent the media button event receiver associated with the client. May be null, which implies there is no registered media button event receiver.
param
clearing true if the new client generation value maps to a remote control update where the display should be cleared.

        // send the new valid client generation ID to all displays
        setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
        // send the new valid client generation ID to all clients
        setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
    
protected voidsetRemoteStreamVolume(int vol)

        if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
        synchronized (mMainRemote) {
            if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
                return;
            }
            rccId = mMainRemote.mRccId;
        }
        IRemoteVolumeObserver rvo = null;
        synchronized (mPRStack) {
            // The stack traversal order doesn't matter because there is only one stack entry
            //  with this RCC ID, but the matching ID is more likely at the top of the stack, so
            //  start iterating from the top.
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
                    if (prse.getRccId() == rccId) {
                        rvo = prse.mRemoteVolumeObs;
                        break;
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
            }
        }
        if (rvo != null) {
            try {
                rvo.dispatchRemoteVolumeUpdate(0, vol);
            } catch (RemoteException e) {
                Log.e(TAG, "Error dispatching absolute volume update", e);
            }
        }
    
private voidstartVoiceBasedInteractions(boolean needWakeLock)
Tell the system to start voice-based interactions / voice commands

        Intent voiceIntent = null;
        // select which type of search to launch:
        // - screen on and device unlocked: action is ACTION_WEB_SEARCH
        // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
        //    with EXTRA_SECURE set to true if the device is securely locked
        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
        boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
        if (!isLocked && pm.isScreenOn()) {
            voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
            Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
        } else {
            voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
            voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
                    isLocked && mKeyguardManager.isKeyguardSecure());
            Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
        }
        // start the search activity
        if (needWakeLock) {
            mMediaEventWakeLock.acquire();
        }
        final long identity = Binder.clearCallingIdentity();
        try {
            if (voiceIntent != null) {
                voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
            }
        } catch (ActivityNotFoundException e) {
            Log.w(TAG, "No activity for search: " + e);
        } finally {
            Binder.restoreCallingIdentity(identity);
            if (needWakeLock) {
                mMediaEventWakeLock.release();
            }
        }
    
protected voidunregisterAudioFocusClient(java.lang.String clientId)

        synchronized(mAudioFocusLock) {
            removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
        }
    
protected voidunregisterMediaButtonEventReceiverForCalls()
see AudioManager.unregisterMediaButtonEventReceiverForCalls()

        if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
                != PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
            return;
        }
        synchronized(mPRStack) {
            mMediaReceiverForCalls = null;
        }
    
protected voidunregisterMediaButtonIntent(android.app.PendingIntent mediaIntent)
see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) precondition: mediaIntent != null, eventReceiver != null

        Log.i(TAG, "  Remote Control   unregisterMediaButtonIntent() for " + mediaIntent);

        synchronized(mPRStack) {
            boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
            removeMediaButtonReceiver_syncPrs(mediaIntent);
            if (topOfStackWillChange) {
                // current RC client will change, assume every type of info needs to be queried
                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
            }
        }
    
protected voidunregisterMediaButtonIntentAsync(android.app.PendingIntent mediaIntent)

        mEventHandler.sendMessage(
                mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
                        mediaIntent));
    
protected voidunregisterRemoteControlClient(android.app.PendingIntent mediaIntent, IRemoteControlClient rcClient)
see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) rcClient is guaranteed non-null

        if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
        synchronized(mPRStack) {
            boolean topRccChange = false;
            try {
                for (int index = mPRStack.size()-1; index >= 0; index--) {
                    final PlayerRecord prse = mPRStack.elementAt(index);
                    if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
                            && rcClient.equals(prse.getRcc())) {
                        // we found the IRemoteControlClient to unregister
                        prse.resetControllerInfoForNoRcc();
                        topRccChange = (index == mPRStack.size()-1);
                        // there can only be one matching RCC in the RC stack, we're done
                        break;
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
            }
            if (topRccChange) {
                // no more RCC for the RCD, check for potential refresh of the remote controls
                checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
            }
        }
    
protected voidunregisterRemoteControlDisplay(IRemoteControlDisplay rcd)
Unregister an IRemoteControlDisplay. No effect if the IRemoteControlDisplay hasn't been successfully registered.

see
android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
param
rcd the IRemoteControlDisplay to unregister. No effect if null.

        if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
        synchronized(mPRStack) {
            if (rcd == null) {
                return;
            }

            boolean displayWasPluggedIn = false;
            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
            while (displayIterator.hasNext() && !displayWasPluggedIn) {
                final DisplayInfoForServer di = displayIterator.next();
                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
                    displayWasPluggedIn = true;
                    di.release();
                    displayIterator.remove();
                }
            }

            if (displayWasPluggedIn) {
                // disconnect this remote control display from all the clients, so the remote
                //   control stack traversal order doesn't matter
                final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
                while(stackIterator.hasNext()) {
                    final PlayerRecord prse = stackIterator.next();
                    if(prse.getRcc() != null) {
                        try {
                            prse.getRcc().unplugRemoteControlDisplay(rcd);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error disconnecting remote control display to client: ", e);
                        }
                    }
                }
            } else {
                if (DEBUG_RC) Log.w(TAG, "  trying to unregister unregistered RCD");
            }
        }
    
private voidupdateRemoteControlDisplay_syncPrs(int infoChangedFlags)
Helper function for code readability: only to be called from checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for this method. Preconditions: - called synchronized on mPRStack - mPRStack.isEmpty() is false

        PlayerRecord prse = mPRStack.peek();
        int infoFlagsAboutToBeUsed = infoChangedFlags;
        // this is where we enforce opt-in for information display on the remote controls
        //   with the new AudioManager.registerRemoteControlClient() API
        if (prse.getRcc() == null) {
            //Log.w(TAG, "Can't update remote control display with null remote control client");
            clearRemoteControlDisplay_syncPrs();
            return;
        }
        synchronized(mCurrentRcLock) {
            if (!prse.getRcc().equals(mCurrentRcClient)) {
                // new RC client, assume every type of information shall be queried
                infoFlagsAboutToBeUsed = RC_INFO_ALL;
            }
            mCurrentRcClient = prse.getRcc();
            mCurrentRcClientIntent = prse.getMediaButtonIntent();
        }
        // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
        mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
                infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );