MediaFocusControlpublic class MediaFocusControl extends Object implements android.app.PendingIntent.OnFinished
Fields Summary |
---|
private static final String | TAG | protected static final boolean | DEBUG_RCDebug remote control client/display feature | protected static final boolean | DEBUG_VOLDebug volumes | private boolean | mIsRingingUsed 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_REPLACEIf the msg is already queued, replace it with this one. | private static final int | SENDMSG_NOOPIf the msg is already queued, ignore this one and leave the old. | private static final int | SENDMSG_QUEUEIf the msg is already queued, queue this one and leave the old. | protected static final String | IN_VOICE_COMM_FOCUS_IDConstant 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 | mNotifyFocusOwnerOnDuckIndicates 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_PRESSThe 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 | mCurrentRcLockSynchronization on mCurrentRcLock always inside a block synchronized on mPRStack | private IRemoteControlClient | mCurrentRcClientThe 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 | mCurrentRcClientIntentThe 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 | mCurrentRcClientGenA 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 | mMainRemoteInternal 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 | mMainRemoteIsActiveIndicates whether the "main" RemoteControlClient is considered active.
Use synchronized on mMainRemote. | private boolean | mHasRemotePlaybackIndicates 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 | mPRStackThe stack of remote control event receivers.
All read and write operations on mPRStack are synchronized. | private android.content.ComponentName | mMediaReceiverForCallsThe component the telephony package can register so telephony calls have priority to
handle media button events | private ArrayList | mRcDisplaysThe 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 int | abandonAudioFocus(IAudioFocusDispatcher fl, java.lang.String clientId, AudioAttributes aa)
// 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;
| void | addFocusFollower(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 boolean | canReassignAudioFocus()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 int | checkRcdRegistrationAuthorization(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
// 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 void | checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags)Helper function:
Called synchronized on mPRStack
Check whether the remote control display should be updated, triggers the update if required
// 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 boolean | checkUpdateRemoteStateIfActive(int streamType)Checks if a remote client is active on the supplied stream type. Update the remote stream
volume state if found and 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 void | clearRemoteControlDisplay_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 void | discardAudioFocusOwner()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 void | dispatchMediaKeyEvent(android.view.KeyEvent keyEvent)No-op if the key code for keyEvent is not a valid media key
(see {@link #isValidMediaKeyEvent(KeyEvent)})
filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
| private void | dispatchMediaKeyEvent(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.
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 void | dispatchMediaKeyEventForCalls(android.view.KeyEvent keyEvent, boolean needWakeLock)Handles the dispatching of the media button events to the telephony package.
Precondition: mMediaReceiverForCalls != null
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 void | dispatchMediaKeyEventUnderWakelock(android.view.KeyEvent keyEvent)No-op if the key code for keyEvent is not a valid media key
(see {@link #isValidMediaKeyEvent(KeyEvent)})
filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
| protected void | dump(java.io.PrintWriter pw)
dumpFocusStack(pw);
dumpRCStack(pw);
dumpRCCStack(pw);
dumpRCDList(pw);
| private void | dumpFocusStack(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 void | dumpRCCStack(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 void | dumpRCDList(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 void | dumpRCStack(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 void | enableRemoteControlDisplayForClient_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 void | filterMediaKeyEvent(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 void | filterVoiceInputKeyEvent(android.view.KeyEvent keyEvent, boolean needWakeLock)Filter key events that may be used for voice-based interactions
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 int | getCurrentAudioFocus()
synchronized(mAudioFocusLock) {
if (mFocusStack.empty()) {
return AudioManager.AUDIOFOCUS_NONE;
} else {
return mFocusStack.peek().getGainRequest();
}
}
| protected int | getRemoteStreamMaxVolume()
synchronized (mMainRemote) {
if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
return 0;
}
return mMainRemote.mVolumeMax;
}
| protected int | getRemoteStreamVolume()
synchronized (mMainRemote) {
if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
return 0;
}
return mMainRemote.mVolume;
}
| private boolean | isComponentInStringArray(android.content.ComponentName comp, java.lang.String[] enabledArray)
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 boolean | isCurrentRcController(android.app.PendingIntent pi)Helper function:
Called synchronized on mPRStack
if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
return true;
}
return false;
| private boolean | isLockedFocusOwner(FocusRequester fr)
return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
| protected static boolean | isPlaystateActive(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
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 boolean | isValidMediaKeyEvent(android.view.KeyEvent keyEvent)
if (keyEvent == null) {
return false;
}
return KeyEvent.isMediaKey(keyEvent.getKeyCode());
| private static boolean | isValidVoiceInputKeyCode(int keyCode)Checks whether the given key code is one that can trigger the launch of voice-based
interactions.
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
return true;
} else {
return false;
}
| boolean | mustNotifyFocusOwnerOnDuck() return mNotifyFocusOwnerOnDuck;
| void | notifyExtPolicyFocusGrant_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);
}
}
| void | notifyExtPolicyFocusLoss_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 void | notifyTopOfAudioFocusStack()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 void | onRcDisplayClear()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 void | onRcDisplayInitInfo(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 void | onRcDisplayUpdate(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 void | onReevaluateRemote()
// 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 void | onReevaluateRemoteControlDisplays()
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 void | onRegisterVolumeObserverForRcc(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 void | onSendFinished(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 void | plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc)Plug each registered display into the specified client
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 void | postReevaluateRemote()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 void | postReevaluateRemoteControlDisplays()
sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
| private void | propagateFocusLossFromGain_syncAf(int focusGain)Focus is requested, propagate the associated loss throughout 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 int | pushBelowLockedFocusOwners(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.
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 boolean | pushMediaButtonReceiver_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
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 boolean | rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd)Is the remote control display interface already registered
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 void | registerMediaButtonEventReceiverForCalls(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 void | registerMediaButtonIntent(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 int | registerRemoteControlClient(android.app.PendingIntent mediaIntent, IRemoteControlClient rcClient, java.lang.String callingPackageName)see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
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 boolean | registerRemoteControlDisplay(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 void | registerRemoteControlDisplay_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.
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 boolean | registerRemoteController(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 void | remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h)Update the size of the artwork used by an IRemoteControlDisplay.
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 void | remoteControlDisplayWantsPlaybackPositionSync(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.
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);
}
}
}
}
| void | removeFocusFollower(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 void | removeFocusStackEntry(java.lang.String clientToRemove, boolean signal, boolean notifyFocusFollowers)Helper function:
Called synchronized on mAudioFocusLock
Remove a focus listener from the focus stack.
// 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 void | removeFocusStackEntryForClient(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 void | removeMediaButtonReceiver_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 int | requestAudioFocus(AudioAttributes aa, int focusChangeHint, android.os.IBinder cb, IAudioFocusDispatcher fd, java.lang.String clientId, java.lang.String callingPackageName, int flags)
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 void | sendMsg(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 void | sendSimulatedMediaButtonEvent(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 void | sendVolumeUpdateToRemote(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 void | setDuckingInExtPolicyAvailable(boolean available)
mNotifyFocusOwnerOnDuck = !available;
| private void | setNewRcClientGenerationOnClients_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 void | setNewRcClientOnDisplays_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 void | setNewRcClient_syncRcsCurrc(int newClientGeneration, android.app.PendingIntent newMediaIntent, boolean clearing)Update the displays and clients with the new "focused" client generation and name
// 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 void | setRemoteStreamVolume(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 void | startVoiceBasedInteractions(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 void | unregisterAudioFocusClient(java.lang.String clientId)
synchronized(mAudioFocusLock) {
removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
}
| protected void | unregisterMediaButtonEventReceiverForCalls()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 void | unregisterMediaButtonIntent(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 void | unregisterMediaButtonIntentAsync(android.app.PendingIntent mediaIntent)
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
mediaIntent));
| protected void | unregisterRemoteControlClient(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 void | unregisterRemoteControlDisplay(IRemoteControlDisplay rcd)Unregister an IRemoteControlDisplay.
No effect if the IRemoteControlDisplay hasn't been successfully registered.
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 void | updateRemoteControlDisplay_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 */) );
|
|