1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.AppOpsManager; 22 import android.app.KeyguardManager; 23 import android.app.PendingIntent; 24 import android.app.PendingIntent.CanceledException; 25 import android.app.PendingIntent.OnFinished; 26 import android.content.ActivityNotFoundException; 27 import android.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.PackageManager; 34 import android.database.ContentObserver; 35 import android.media.PlayerRecord.RemotePlaybackState; 36 import android.media.audiopolicy.IAudioPolicyCallback; 37 import android.net.Uri; 38 import android.os.Binder; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.PowerManager; 45 import android.os.RemoteException; 46 import android.os.UserHandle; 47 import android.os.IBinder.DeathRecipient; 48 import android.provider.Settings; 49 import android.speech.RecognizerIntent; 50 import android.telephony.PhoneStateListener; 51 import android.telephony.TelephonyManager; 52 import android.util.Log; 53 import android.util.Slog; 54 import android.view.KeyEvent; 55 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.Iterator; 59 import java.util.Stack; 60 61 /** 62 * @hide 63 * 64 */ 65 public class MediaFocusControl implements OnFinished { 66 67 private static final String TAG = "MediaFocusControl"; 68 69 /** Debug remote control client/display feature */ 70 protected static final boolean DEBUG_RC = false; 71 /** Debug volumes */ 72 protected static final boolean DEBUG_VOL = false; 73 74 /** Used to alter media button redirection when the phone is ringing. */ 75 private boolean mIsRinging = false; 76 77 private final PowerManager.WakeLock mMediaEventWakeLock; 78 private final MediaEventHandler mEventHandler; 79 private final Context mContext; 80 private final ContentResolver mContentResolver; 81 private final AudioService.VolumeController mVolumeController; 82 private final AppOpsManager mAppOps; 83 private final KeyguardManager mKeyguardManager; 84 private final AudioService mAudioService; 85 private final NotificationListenerObserver mNotifListenerObserver; 86 MediaFocusControl(Looper looper, Context cntxt, AudioService.VolumeController volumeCtrl, AudioService as)87 protected MediaFocusControl(Looper looper, Context cntxt, 88 AudioService.VolumeController volumeCtrl, AudioService as) { 89 mEventHandler = new MediaEventHandler(looper); 90 mContext = cntxt; 91 mContentResolver = mContext.getContentResolver(); 92 mVolumeController = volumeCtrl; 93 mAudioService = as; 94 95 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 96 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); 97 mMainRemote = new RemotePlaybackState(-1, 98 AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC), 99 AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC)); 100 101 // Register for phone state monitoring 102 TelephonyManager tmgr = (TelephonyManager) 103 mContext.getSystemService(Context.TELEPHONY_SERVICE); 104 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 105 106 mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); 107 mKeyguardManager = 108 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 109 mNotifListenerObserver = new NotificationListenerObserver(); 110 111 mHasRemotePlayback = false; 112 mMainRemoteIsActive = false; 113 114 PlayerRecord.setMediaFocusControl(this); 115 116 postReevaluateRemote(); 117 } 118 dump(PrintWriter pw)119 protected void dump(PrintWriter pw) { 120 dumpFocusStack(pw); 121 dumpRCStack(pw); 122 dumpRCCStack(pw); 123 dumpRCDList(pw); 124 } 125 126 //========================================================================================== 127 // Management of RemoteControlDisplay registration permissions 128 //========================================================================================== 129 private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI = 130 Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); 131 132 private class NotificationListenerObserver extends ContentObserver { 133 NotificationListenerObserver()134 NotificationListenerObserver() { 135 super(mEventHandler); 136 mContentResolver.registerContentObserver(Settings.Secure.getUriFor( 137 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); 138 } 139 140 @Override onChange(boolean selfChange, Uri uri)141 public void onChange(boolean selfChange, Uri uri) { 142 if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) { 143 return; 144 } 145 if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); } 146 postReevaluateRemoteControlDisplays(); 147 } 148 } 149 150 private final static int RCD_REG_FAILURE = 0; 151 private final static int RCD_REG_SUCCESS_PERMISSION = 1; 152 private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2; 153 154 /** 155 * Checks a caller's authorization to register an IRemoteControlDisplay. 156 * Authorization is granted if one of the following is true: 157 * <ul> 158 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li> 159 * <li>the caller's listener is one of the enabled notification listeners</li> 160 * </ul> 161 * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay 162 * registration. 163 */ checkRcdRegistrationAuthorization(ComponentName listenerComp)164 private int checkRcdRegistrationAuthorization(ComponentName listenerComp) { 165 // MEDIA_CONTENT_CONTROL permission check 166 if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 167 android.Manifest.permission.MEDIA_CONTENT_CONTROL)) { 168 if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");} 169 return RCD_REG_SUCCESS_PERMISSION; 170 } 171 172 // ENABLED_NOTIFICATION_LISTENERS settings check 173 if (listenerComp != null) { 174 // this call is coming from an app, can't use its identity to read secure settings 175 final long ident = Binder.clearCallingIdentity(); 176 try { 177 final int currentUser = ActivityManager.getCurrentUser(); 178 final String enabledNotifListeners = Settings.Secure.getStringForUser( 179 mContext.getContentResolver(), 180 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 181 currentUser); 182 if (enabledNotifListeners != null) { 183 final String[] components = enabledNotifListeners.split(":"); 184 for (int i=0; i<components.length; i++) { 185 final ComponentName component = 186 ComponentName.unflattenFromString(components[i]); 187 if (component != null) { 188 if (listenerComp.equals(component)) { 189 if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component + 190 " is authorized notification listener"); } 191 return RCD_REG_SUCCESS_ENABLED_NOTIF; 192 } 193 } 194 } 195 } 196 if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp + 197 " is not in list of ENABLED_NOTIFICATION_LISTENERS"); } 198 } finally { 199 Binder.restoreCallingIdentity(ident); 200 } 201 } 202 203 return RCD_REG_FAILURE; 204 } 205 registerRemoteController(IRemoteControlDisplay rcd, int w, int h, ComponentName listenerComp)206 protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, 207 ComponentName listenerComp) { 208 int reg = checkRcdRegistrationAuthorization(listenerComp); 209 if (reg != RCD_REG_FAILURE) { 210 registerRemoteControlDisplay_int(rcd, w, h, listenerComp); 211 return true; 212 } else { 213 Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + 214 ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + 215 " or be an enabled NotificationListenerService for registerRemoteController"); 216 return false; 217 } 218 } 219 registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h)220 protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { 221 int reg = checkRcdRegistrationAuthorization(null); 222 if (reg != RCD_REG_FAILURE) { 223 registerRemoteControlDisplay_int(rcd, w, h, null); 224 return true; 225 } else { 226 Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + 227 ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + 228 " to register IRemoteControlDisplay"); 229 return false; 230 } 231 } 232 postReevaluateRemoteControlDisplays()233 private void postReevaluateRemoteControlDisplays() { 234 sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0); 235 } 236 onReevaluateRemoteControlDisplays()237 private void onReevaluateRemoteControlDisplays() { 238 if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); } 239 // read which components are enabled notification listeners 240 final int currentUser = ActivityManager.getCurrentUser(); 241 final String enabledNotifListeners = Settings.Secure.getStringForUser( 242 mContext.getContentResolver(), 243 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 244 currentUser); 245 if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); } 246 synchronized(mAudioFocusLock) { 247 synchronized(mPRStack) { 248 // check whether the "enable" status of each RCD with a notification listener 249 // has changed 250 final String[] enabledComponents; 251 if (enabledNotifListeners == null) { 252 enabledComponents = null; 253 } else { 254 enabledComponents = enabledNotifListeners.split(":"); 255 } 256 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 257 while (displayIterator.hasNext()) { 258 final DisplayInfoForServer di = 259 displayIterator.next(); 260 if (di.mClientNotifListComp != null) { 261 boolean wasEnabled = di.mEnabled; 262 di.mEnabled = isComponentInStringArray(di.mClientNotifListComp, 263 enabledComponents); 264 if (wasEnabled != di.mEnabled){ 265 try { 266 // tell the RCD whether it's enabled 267 di.mRcDisplay.setEnabled(di.mEnabled); 268 // tell the RCCs about the change for this RCD 269 enableRemoteControlDisplayForClient_syncRcStack( 270 di.mRcDisplay, di.mEnabled); 271 // when enabling, refresh the information on the display 272 if (di.mEnabled) { 273 sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, 274 di.mArtworkExpectedWidth /*arg1*/, 275 di.mArtworkExpectedHeight/*arg2*/, 276 di.mRcDisplay /*obj*/, 0/*delay*/); 277 } 278 } catch (RemoteException e) { 279 Log.e(TAG, "Error en/disabling RCD: ", e); 280 } 281 } 282 } 283 } 284 } 285 } 286 } 287 288 /** 289 * @param comp a non-null ComponentName 290 * @param enabledArray may be null 291 * @return 292 */ isComponentInStringArray(ComponentName comp, String[] enabledArray)293 private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) { 294 if (enabledArray == null || enabledArray.length == 0) { 295 if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); } 296 return false; 297 } 298 final String compString = comp.flattenToString(); 299 for (int i=0; i<enabledArray.length; i++) { 300 if (compString.equals(enabledArray[i])) { 301 if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); } 302 return true; 303 } 304 } 305 if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); } 306 return false; 307 } 308 309 //========================================================================================== 310 // Internal event handling 311 //========================================================================================== 312 313 // event handler messages 314 private static final int MSG_RCDISPLAY_CLEAR = 1; 315 private static final int MSG_RCDISPLAY_UPDATE = 2; 316 private static final int MSG_REEVALUATE_REMOTE = 3; 317 private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4; 318 private static final int MSG_RCC_NEW_VOLUME_OBS = 5; 319 private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6; 320 private static final int MSG_RCC_SEEK_REQUEST = 7; 321 private static final int MSG_RCC_UPDATE_METADATA = 8; 322 private static final int MSG_RCDISPLAY_INIT_INFO = 9; 323 private static final int MSG_REEVALUATE_RCD = 10; 324 private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11; 325 326 // sendMsg() flags 327 /** If the msg is already queued, replace it with this one. */ 328 private static final int SENDMSG_REPLACE = 0; 329 /** If the msg is already queued, ignore this one and leave the old. */ 330 private static final int SENDMSG_NOOP = 1; 331 /** If the msg is already queued, queue this one and leave the old. */ 332 private static final int SENDMSG_QUEUE = 2; 333 sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay)334 private static void sendMsg(Handler handler, int msg, 335 int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { 336 337 if (existingMsgPolicy == SENDMSG_REPLACE) { 338 handler.removeMessages(msg); 339 } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { 340 return; 341 } 342 343 handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); 344 } 345 346 private class MediaEventHandler extends Handler { MediaEventHandler(Looper looper)347 MediaEventHandler(Looper looper) { 348 super(looper); 349 } 350 351 @Override handleMessage(Message msg)352 public void handleMessage(Message msg) { 353 switch(msg.what) { 354 case MSG_RCDISPLAY_CLEAR: 355 onRcDisplayClear(); 356 break; 357 358 case MSG_RCDISPLAY_UPDATE: 359 // msg.obj is guaranteed to be non null 360 onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1); 361 break; 362 363 case MSG_REEVALUATE_REMOTE: 364 onReevaluateRemote(); 365 break; 366 367 case MSG_RCC_NEW_VOLUME_OBS: 368 onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, 369 (IRemoteVolumeObserver)msg.obj /* rvo */); 370 break; 371 372 case MSG_RCDISPLAY_INIT_INFO: 373 // msg.obj is guaranteed to be non null 374 onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/, 375 msg.arg1/*w*/, msg.arg2/*h*/); 376 break; 377 378 case MSG_REEVALUATE_RCD: 379 onReevaluateRemoteControlDisplays(); 380 break; 381 382 case MSG_UNREGISTER_MEDIABUTTONINTENT: 383 unregisterMediaButtonIntent( (PendingIntent) msg.obj ); 384 break; 385 } 386 } 387 } 388 389 390 //========================================================================================== 391 // AudioFocus 392 //========================================================================================== 393 394 /** 395 * Constant to identify a focus stack entry that is used to hold the focus while the phone 396 * is ringing or during a call. Used by com.android.internal.telephony.CallManager when 397 * entering and exiting calls. 398 */ 399 protected final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; 400 401 private final static Object mAudioFocusLock = new Object(); 402 403 private final static Object mRingingLock = new Object(); 404 405 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 406 @Override 407 public void onCallStateChanged(int state, String incomingNumber) { 408 if (state == TelephonyManager.CALL_STATE_RINGING) { 409 //Log.v(TAG, " CALL_STATE_RINGING"); 410 synchronized(mRingingLock) { 411 mIsRinging = true; 412 } 413 } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) 414 || (state == TelephonyManager.CALL_STATE_IDLE)) { 415 synchronized(mRingingLock) { 416 mIsRinging = false; 417 } 418 } 419 } 420 }; 421 422 /** 423 * Discard the current audio focus owner. 424 * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign 425 * focus), remove it from the stack, and clear the remote control display. 426 */ discardAudioFocusOwner()427 protected void discardAudioFocusOwner() { 428 synchronized(mAudioFocusLock) { 429 if (!mFocusStack.empty()) { 430 // notify the current focus owner it lost focus after removing it from stack 431 final FocusRequester exFocusOwner = mFocusStack.pop(); 432 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); 433 exFocusOwner.release(); 434 } 435 } 436 } 437 438 /** 439 * Called synchronized on mAudioFocusLock 440 */ notifyTopOfAudioFocusStack()441 private void notifyTopOfAudioFocusStack() { 442 // notify the top of the stack it gained focus 443 if (!mFocusStack.empty()) { 444 if (canReassignAudioFocus()) { 445 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); 446 } 447 } 448 } 449 450 /** 451 * Focus is requested, propagate the associated loss throughout the stack. 452 * @param focusGain the new focus gain that will later be added at the top of the stack 453 */ propagateFocusLossFromGain_syncAf(int focusGain)454 private void propagateFocusLossFromGain_syncAf(int focusGain) { 455 // going through the audio focus stack to signal new focus, traversing order doesn't 456 // matter as all entries respond to the same external focus gain 457 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 458 while(stackIterator.hasNext()) { 459 stackIterator.next().handleExternalFocusGain(focusGain); 460 } 461 } 462 463 private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); 464 465 /** 466 * Helper function: 467 * Display in the log the current entries in the audio focus stack 468 */ dumpFocusStack(PrintWriter pw)469 private void dumpFocusStack(PrintWriter pw) { 470 pw.println("\nAudio Focus stack entries (last is top of stack):"); 471 synchronized(mAudioFocusLock) { 472 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 473 while(stackIterator.hasNext()) { 474 stackIterator.next().dump(pw); 475 } 476 } 477 pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n"); 478 } 479 480 /** 481 * Helper function: 482 * Called synchronized on mAudioFocusLock 483 * Remove a focus listener from the focus stack. 484 * @param clientToRemove the focus listener 485 * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding 486 * focus, notify the next item in the stack it gained focus. 487 */ removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers)488 private void removeFocusStackEntry(String clientToRemove, boolean signal, 489 boolean notifyFocusFollowers) { 490 // is the current top of the focus stack abandoning focus? (because of request, not death) 491 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) 492 { 493 //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); 494 FocusRequester fr = mFocusStack.pop(); 495 fr.release(); 496 if (notifyFocusFollowers) { 497 final AudioFocusInfo afi = fr.toAudioFocusInfo(); 498 afi.clearLossReceived(); 499 notifyExtPolicyFocusLoss_syncAf(afi, false); 500 } 501 if (signal) { 502 // notify the new top of the stack it gained focus 503 notifyTopOfAudioFocusStack(); 504 } 505 } else { 506 // focus is abandoned by a client that's not at the top of the stack, 507 // no need to update focus. 508 // (using an iterator on the stack so we can safely remove an entry after having 509 // evaluated it, traversal order doesn't matter here) 510 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 511 while(stackIterator.hasNext()) { 512 FocusRequester fr = stackIterator.next(); 513 if(fr.hasSameClient(clientToRemove)) { 514 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " 515 + clientToRemove); 516 stackIterator.remove(); 517 fr.release(); 518 } 519 } 520 } 521 } 522 523 /** 524 * Helper function: 525 * Called synchronized on mAudioFocusLock 526 * Remove focus listeners from the focus stack for a particular client when it has died. 527 */ removeFocusStackEntryForClient(IBinder cb)528 private void removeFocusStackEntryForClient(IBinder cb) { 529 // is the owner of the audio focus part of the client to remove? 530 boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && 531 mFocusStack.peek().hasSameBinder(cb); 532 // (using an iterator on the stack so we can safely remove an entry after having 533 // evaluated it, traversal order doesn't matter here) 534 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 535 while(stackIterator.hasNext()) { 536 FocusRequester fr = stackIterator.next(); 537 if(fr.hasSameBinder(cb)) { 538 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb); 539 stackIterator.remove(); 540 // the client just died, no need to unlink to its death 541 } 542 } 543 if (isTopOfStackForClientToRemove) { 544 // we removed an entry at the top of the stack: 545 // notify the new top of the stack it gained focus. 546 notifyTopOfAudioFocusStack(); 547 } 548 } 549 550 /** 551 * Helper function: 552 * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. 553 * The implementation guarantees that a state where focus cannot be immediately reassigned 554 * implies that an "locked" focus owner is at the top of the focus stack. 555 * Modifications to the implementation that break this assumption will cause focus requests to 556 * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag. 557 */ canReassignAudioFocus()558 private boolean canReassignAudioFocus() { 559 // focus requests are rejected during a phone call or when the phone is ringing 560 // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus 561 if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) { 562 return false; 563 } 564 return true; 565 } 566 isLockedFocusOwner(FocusRequester fr)567 private boolean isLockedFocusOwner(FocusRequester fr) { 568 return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner()); 569 } 570 571 /** 572 * Helper function 573 * Pre-conditions: focus stack is not empty, there is one or more locked focus owner 574 * at the top of the focus stack 575 * Push the focus requester onto the audio focus stack at the first position immediately 576 * following the locked focus owners. 577 * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or 578 * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} 579 */ pushBelowLockedFocusOwners(FocusRequester nfr)580 private int pushBelowLockedFocusOwners(FocusRequester nfr) { 581 int lastLockedFocusOwnerIndex = mFocusStack.size(); 582 for (int index = mFocusStack.size()-1; index >= 0; index--) { 583 if (isLockedFocusOwner(mFocusStack.elementAt(index))) { 584 lastLockedFocusOwnerIndex = index; 585 } 586 } 587 if (lastLockedFocusOwnerIndex == mFocusStack.size()) { 588 // this should not happen, but handle it and log an error 589 Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()", 590 new Exception()); 591 // no exclusive owner, push at top of stack, focus is granted, propagate change 592 propagateFocusLossFromGain_syncAf(nfr.getGainRequest()); 593 mFocusStack.push(nfr); 594 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 595 } else { 596 mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); 597 return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 598 } 599 } 600 601 /** 602 * Inner class to monitor audio focus client deaths, and remove them from the audio focus 603 * stack if necessary. 604 */ 605 protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { 606 private IBinder mCb; // To be notified of client's death 607 AudioFocusDeathHandler(IBinder cb)608 AudioFocusDeathHandler(IBinder cb) { 609 mCb = cb; 610 } 611 binderDied()612 public void binderDied() { 613 synchronized(mAudioFocusLock) { 614 Log.w(TAG, " AudioFocus audio focus client died"); 615 removeFocusStackEntryForClient(mCb); 616 } 617 } 618 getBinder()619 public IBinder getBinder() { 620 return mCb; 621 } 622 } 623 624 /** 625 * Indicates whether to notify an audio focus owner when it loses focus 626 * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck. 627 * This variable being false indicates an AudioPolicy has been registered and has signaled 628 * it will handle audio ducking. 629 */ 630 private boolean mNotifyFocusOwnerOnDuck = true; 631 setDuckingInExtPolicyAvailable(boolean available)632 protected void setDuckingInExtPolicyAvailable(boolean available) { 633 mNotifyFocusOwnerOnDuck = !available; 634 } 635 mustNotifyFocusOwnerOnDuck()636 boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; } 637 638 private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>(); 639 addFocusFollower(IAudioPolicyCallback ff)640 void addFocusFollower(IAudioPolicyCallback ff) { 641 if (ff == null) { 642 return; 643 } 644 synchronized(mAudioFocusLock) { 645 boolean found = false; 646 for (IAudioPolicyCallback pcb : mFocusFollowers) { 647 if (pcb.asBinder().equals(ff.asBinder())) { 648 found = true; 649 break; 650 } 651 } 652 if (found) { 653 return; 654 } else { 655 mFocusFollowers.add(ff); 656 } 657 } 658 } 659 removeFocusFollower(IAudioPolicyCallback ff)660 void removeFocusFollower(IAudioPolicyCallback ff) { 661 if (ff == null) { 662 return; 663 } 664 synchronized(mAudioFocusLock) { 665 for (IAudioPolicyCallback pcb : mFocusFollowers) { 666 if (pcb.asBinder().equals(ff.asBinder())) { 667 mFocusFollowers.remove(pcb); 668 break; 669 } 670 } 671 } 672 } 673 674 /** 675 * Called synchronized on mAudioFocusLock 676 */ notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)677 void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) { 678 for (IAudioPolicyCallback pcb : mFocusFollowers) { 679 try { 680 // oneway 681 pcb.notifyAudioFocusGrant(afi, requestResult); 682 } catch (RemoteException e) { 683 Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback " 684 + pcb.asBinder(), e); 685 } 686 } 687 } 688 689 /** 690 * Called synchronized on mAudioFocusLock 691 */ notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)692 void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) { 693 for (IAudioPolicyCallback pcb : mFocusFollowers) { 694 try { 695 // oneway 696 pcb.notifyAudioFocusLoss(afi, wasDispatched); 697 } catch (RemoteException e) { 698 Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback " 699 + pcb.asBinder(), e); 700 } 701 } 702 } 703 getCurrentAudioFocus()704 protected int getCurrentAudioFocus() { 705 synchronized(mAudioFocusLock) { 706 if (mFocusStack.empty()) { 707 return AudioManager.AUDIOFOCUS_NONE; 708 } else { 709 return mFocusStack.peek().getGainRequest(); 710 } 711 } 712 } 713 714 /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */ requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags)715 protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, 716 IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) { 717 Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint + 718 "flags=0x" + Integer.toHexString(flags)); 719 // we need a valid binder callback for clients 720 if (!cb.pingBinder()) { 721 Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); 722 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 723 } 724 725 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), 726 callingPackageName) != AppOpsManager.MODE_ALLOWED) { 727 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 728 } 729 730 synchronized(mAudioFocusLock) { 731 boolean focusGrantDelayed = false; 732 if (!canReassignAudioFocus()) { 733 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { 734 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 735 } else { 736 // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be 737 // granted right now, so the requester will be inserted in the focus stack 738 // to receive focus later 739 focusGrantDelayed = true; 740 } 741 } 742 743 // handle the potential premature death of the new holder of the focus 744 // (premature death == death before abandoning focus) 745 // Register for client death notification 746 AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); 747 try { 748 cb.linkToDeath(afdh, 0); 749 } catch (RemoteException e) { 750 // client has already died! 751 Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); 752 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 753 } 754 755 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { 756 // if focus is already owned by this client and the reason for acquiring the focus 757 // hasn't changed, don't do anything 758 final FocusRequester fr = mFocusStack.peek(); 759 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) { 760 // unlink death handler so it can be gc'ed. 761 // linkToDeath() creates a JNI global reference preventing collection. 762 cb.unlinkToDeath(afdh, 0); 763 notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(), 764 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 765 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 766 } 767 // the reason for the audio focus request has changed: remove the current top of 768 // stack and respond as if we had a new focus owner 769 if (!focusGrantDelayed) { 770 mFocusStack.pop(); 771 // the entry that was "popped" is the same that was "peeked" above 772 fr.release(); 773 } 774 } 775 776 // focus requester might already be somewhere below in the stack, remove it 777 removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/); 778 779 final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, 780 clientId, afdh, callingPackageName, Binder.getCallingUid(), this); 781 if (focusGrantDelayed) { 782 // focusGrantDelayed being true implies we can't reassign focus right now 783 // which implies the focus stack is not empty. 784 final int requestResult = pushBelowLockedFocusOwners(nfr); 785 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 786 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); 787 } 788 return requestResult; 789 } else { 790 // propagate the focus change through the stack 791 if (!mFocusStack.empty()) { 792 propagateFocusLossFromGain_syncAf(focusChangeHint); 793 } 794 795 // push focus requester at the top of the audio focus stack 796 mFocusStack.push(nfr); 797 } 798 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), 799 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 800 801 }//synchronized(mAudioFocusLock) 802 803 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 804 } 805 806 /** 807 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 808 * */ abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa)809 protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { 810 // AudioAttributes are currently ignored, to be used for zones 811 Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); 812 try { 813 // this will take care of notifying the new focus owner if needed 814 synchronized(mAudioFocusLock) { 815 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/); 816 } 817 } catch (java.util.ConcurrentModificationException cme) { 818 // Catching this exception here is temporary. It is here just to prevent 819 // a crash seen when the "Silent" notification is played. This is believed to be fixed 820 // but this try catch block is left just to be safe. 821 Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); 822 cme.printStackTrace(); 823 } 824 825 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 826 } 827 828 unregisterAudioFocusClient(String clientId)829 protected void unregisterAudioFocusClient(String clientId) { 830 synchronized(mAudioFocusLock) { 831 removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/); 832 } 833 } 834 835 836 //========================================================================================== 837 // RemoteControl 838 //========================================================================================== 839 /** 840 * No-op if the key code for keyEvent is not a valid media key 841 * (see {@link #isValidMediaKeyEvent(KeyEvent)}) 842 * @param keyEvent the key event to send 843 */ dispatchMediaKeyEvent(KeyEvent keyEvent)844 protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { 845 filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); 846 } 847 848 /** 849 * No-op if the key code for keyEvent is not a valid media key 850 * (see {@link #isValidMediaKeyEvent(KeyEvent)}) 851 * @param keyEvent the key event to send 852 */ dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent)853 protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { 854 filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); 855 } 856 filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock)857 private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { 858 // sanity check on the incoming key event 859 if (!isValidMediaKeyEvent(keyEvent)) { 860 Log.e(TAG, "not dispatching invalid media key event " + keyEvent); 861 return; 862 } 863 // event filtering for telephony 864 synchronized(mRingingLock) { 865 synchronized(mPRStack) { 866 if ((mMediaReceiverForCalls != null) && 867 (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) { 868 dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); 869 return; 870 } 871 } 872 } 873 // event filtering based on voice-based interactions 874 if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { 875 filterVoiceInputKeyEvent(keyEvent, needWakeLock); 876 } else { 877 dispatchMediaKeyEvent(keyEvent, needWakeLock); 878 } 879 } 880 881 /** 882 * Handles the dispatching of the media button events to the telephony package. 883 * Precondition: mMediaReceiverForCalls != null 884 * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons 885 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event 886 * is dispatched. 887 */ dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock)888 private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { 889 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); 890 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 891 keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); 892 if (needWakeLock) { 893 mMediaEventWakeLock.acquire(); 894 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); 895 } 896 final long ident = Binder.clearCallingIdentity(); 897 try { 898 mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, 899 null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null); 900 } finally { 901 Binder.restoreCallingIdentity(ident); 902 } 903 } 904 905 /** 906 * Handles the dispatching of the media button events to one of the registered listeners, 907 * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. 908 * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons 909 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event 910 * is dispatched. 911 */ dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock)912 private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { 913 if (needWakeLock) { 914 mMediaEventWakeLock.acquire(); 915 } 916 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); 917 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 918 synchronized(mPRStack) { 919 if (!mPRStack.empty()) { 920 // send the intent that was registered by the client 921 try { 922 mPRStack.peek().getMediaButtonIntent().send(mContext, 923 needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, 924 keyIntent, this, mEventHandler); 925 } catch (CanceledException e) { 926 Log.e(TAG, "Error sending pending intent " + mPRStack.peek()); 927 e.printStackTrace(); 928 } 929 } else { 930 // legacy behavior when nobody registered their media button event receiver 931 // through AudioManager 932 if (needWakeLock) { 933 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); 934 } 935 final long ident = Binder.clearCallingIdentity(); 936 try { 937 mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, 938 null, mKeyEventDone, 939 mEventHandler, Activity.RESULT_OK, null, null); 940 } finally { 941 Binder.restoreCallingIdentity(ident); 942 } 943 } 944 } 945 } 946 947 /** 948 * The different actions performed in response to a voice button key event. 949 */ 950 private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; 951 private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; 952 private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; 953 954 private final Object mVoiceEventLock = new Object(); 955 private boolean mVoiceButtonDown; 956 private boolean mVoiceButtonHandled; 957 958 /** 959 * Filter key events that may be used for voice-based interactions 960 * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported 961 * media buttons that can be used to trigger voice-based interactions. 962 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event 963 * is dispatched. 964 */ filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock)965 private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { 966 if (DEBUG_RC) { 967 Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); 968 } 969 970 int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; 971 int keyAction = keyEvent.getAction(); 972 synchronized (mVoiceEventLock) { 973 if (keyAction == KeyEvent.ACTION_DOWN) { 974 if (keyEvent.getRepeatCount() == 0) { 975 // initial down 976 mVoiceButtonDown = true; 977 mVoiceButtonHandled = false; 978 } else if (mVoiceButtonDown && !mVoiceButtonHandled 979 && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { 980 // long-press, start voice-based interactions 981 mVoiceButtonHandled = true; 982 voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; 983 } 984 } else if (keyAction == KeyEvent.ACTION_UP) { 985 if (mVoiceButtonDown) { 986 // voice button up 987 mVoiceButtonDown = false; 988 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { 989 voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; 990 } 991 } 992 } 993 }//synchronized (mVoiceEventLock) 994 995 // take action after media button event filtering for voice-based interactions 996 switch (voiceButtonAction) { 997 case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: 998 if (DEBUG_RC) Log.v(TAG, " ignore key event"); 999 break; 1000 case VOICEBUTTON_ACTION_START_VOICE_INPUT: 1001 if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); 1002 // then start the voice-based interactions 1003 startVoiceBasedInteractions(needWakeLock); 1004 break; 1005 case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: 1006 if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); 1007 sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); 1008 break; 1009 } 1010 } 1011 sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock)1012 private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { 1013 // send DOWN event 1014 KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); 1015 dispatchMediaKeyEvent(keyEvent, needWakeLock); 1016 // send UP event 1017 keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); 1018 dispatchMediaKeyEvent(keyEvent, needWakeLock); 1019 1020 } 1021 isValidMediaKeyEvent(KeyEvent keyEvent)1022 private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { 1023 if (keyEvent == null) { 1024 return false; 1025 } 1026 return KeyEvent.isMediaKey(keyEvent.getKeyCode()); 1027 } 1028 1029 /** 1030 * Checks whether the given key code is one that can trigger the launch of voice-based 1031 * interactions. 1032 * @param keyCode the key code associated with the key event 1033 * @return true if the key is one of the supported voice-based interaction triggers 1034 */ isValidVoiceInputKeyCode(int keyCode)1035 private static boolean isValidVoiceInputKeyCode(int keyCode) { 1036 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { 1037 return true; 1038 } else { 1039 return false; 1040 } 1041 } 1042 1043 /** 1044 * Tell the system to start voice-based interactions / voice commands 1045 */ startVoiceBasedInteractions(boolean needWakeLock)1046 private void startVoiceBasedInteractions(boolean needWakeLock) { 1047 Intent voiceIntent = null; 1048 // select which type of search to launch: 1049 // - screen on and device unlocked: action is ACTION_WEB_SEARCH 1050 // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE 1051 // with EXTRA_SECURE set to true if the device is securely locked 1052 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 1053 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 1054 if (!isLocked && pm.isScreenOn()) { 1055 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); 1056 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); 1057 } else { 1058 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); 1059 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, 1060 isLocked && mKeyguardManager.isKeyguardSecure()); 1061 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); 1062 } 1063 // start the search activity 1064 if (needWakeLock) { 1065 mMediaEventWakeLock.acquire(); 1066 } 1067 final long identity = Binder.clearCallingIdentity(); 1068 try { 1069 if (voiceIntent != null) { 1070 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1071 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1072 mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT); 1073 } 1074 } catch (ActivityNotFoundException e) { 1075 Log.w(TAG, "No activity for search: " + e); 1076 } finally { 1077 Binder.restoreCallingIdentity(identity); 1078 if (needWakeLock) { 1079 mMediaEventWakeLock.release(); 1080 } 1081 } 1082 } 1083 1084 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number 1085 1086 // only set when wakelock was acquired, no need to check value when received 1087 private static final String EXTRA_WAKELOCK_ACQUIRED = 1088 "android.media.AudioService.WAKELOCK_ACQUIRED"; 1089 onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras)1090 public void onSendFinished(PendingIntent pendingIntent, Intent intent, 1091 int resultCode, String resultData, Bundle resultExtras) { 1092 if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { 1093 mMediaEventWakeLock.release(); 1094 } 1095 } 1096 1097 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { 1098 public void onReceive(Context context, Intent intent) { 1099 if (intent == null) { 1100 return; 1101 } 1102 Bundle extras = intent.getExtras(); 1103 if (extras == null) { 1104 return; 1105 } 1106 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { 1107 mMediaEventWakeLock.release(); 1108 } 1109 } 1110 }; 1111 1112 /** 1113 * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack 1114 */ 1115 private final Object mCurrentRcLock = new Object(); 1116 /** 1117 * The one remote control client which will receive a request for display information. 1118 * This object may be null. 1119 * Access protected by mCurrentRcLock. 1120 */ 1121 private IRemoteControlClient mCurrentRcClient = null; 1122 /** 1123 * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant 1124 * if mCurrentRcClient is null 1125 */ 1126 private PendingIntent mCurrentRcClientIntent = null; 1127 1128 private final static int RC_INFO_NONE = 0; 1129 private final static int RC_INFO_ALL = 1130 RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | 1131 RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | 1132 RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | 1133 RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; 1134 1135 /** 1136 * A monotonically increasing generation counter for mCurrentRcClient. 1137 * Only accessed with a lock on mCurrentRcLock. 1138 * No value wrap-around issues as we only act on equal values. 1139 */ 1140 private int mCurrentRcClientGen = 0; 1141 1142 1143 /** 1144 * Internal cache for the playback information of the RemoteControlClient whose volume gets to 1145 * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack 1146 * every time we need this info. 1147 */ 1148 private RemotePlaybackState mMainRemote; 1149 /** 1150 * Indicates whether the "main" RemoteControlClient is considered active. 1151 * Use synchronized on mMainRemote. 1152 */ 1153 private boolean mMainRemoteIsActive; 1154 /** 1155 * Indicates whether there is remote playback going on. True even if there is no "active" 1156 * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it 1157 * handles remote playback. 1158 * Use synchronized on mMainRemote. 1159 */ 1160 private boolean mHasRemotePlayback; 1161 1162 /** 1163 * The stack of remote control event receivers. 1164 * All read and write operations on mPRStack are synchronized. 1165 */ 1166 private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>(); 1167 1168 /** 1169 * The component the telephony package can register so telephony calls have priority to 1170 * handle media button events 1171 */ 1172 private ComponentName mMediaReceiverForCalls = null; 1173 1174 /** 1175 * Helper function: 1176 * Display in the log the current entries in the remote control focus stack 1177 */ dumpRCStack(PrintWriter pw)1178 private void dumpRCStack(PrintWriter pw) { 1179 pw.println("\nRemote Control stack entries (last is top of stack):"); 1180 synchronized(mPRStack) { 1181 Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1182 while(stackIterator.hasNext()) { 1183 stackIterator.next().dump(pw, true); 1184 } 1185 } 1186 } 1187 1188 /** 1189 * Helper function: 1190 * Display in the log the current entries in the remote control stack, focusing 1191 * on RemoteControlClient data 1192 */ dumpRCCStack(PrintWriter pw)1193 private void dumpRCCStack(PrintWriter pw) { 1194 pw.println("\nRemote Control Client stack entries (last is top of stack):"); 1195 synchronized(mPRStack) { 1196 Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1197 while(stackIterator.hasNext()) { 1198 stackIterator.next().dump(pw, false); 1199 } 1200 synchronized(mCurrentRcLock) { 1201 pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); 1202 } 1203 } 1204 synchronized (mMainRemote) { 1205 pw.println("\nRemote Volume State:"); 1206 pw.println(" has remote: " + mHasRemotePlayback); 1207 pw.println(" is remote active: " + mMainRemoteIsActive); 1208 pw.println(" rccId: " + mMainRemote.mRccId); 1209 pw.println(" volume handling: " 1210 + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? 1211 "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); 1212 pw.println(" volume: " + mMainRemote.mVolume); 1213 pw.println(" volume steps: " + mMainRemote.mVolumeMax); 1214 } 1215 } 1216 1217 /** 1218 * Helper function: 1219 * Display in the log the current entries in the list of remote control displays 1220 */ dumpRCDList(PrintWriter pw)1221 private void dumpRCDList(PrintWriter pw) { 1222 pw.println("\nRemote Control Display list entries:"); 1223 synchronized(mPRStack) { 1224 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1225 while (displayIterator.hasNext()) { 1226 final DisplayInfoForServer di = displayIterator.next(); 1227 pw.println(" IRCD: " + di.mRcDisplay + 1228 " -- w:" + di.mArtworkExpectedWidth + 1229 " -- h:" + di.mArtworkExpectedHeight + 1230 " -- wantsPosSync:" + di.mWantsPositionSync + 1231 " -- " + (di.mEnabled ? "enabled" : "disabled")); 1232 } 1233 } 1234 } 1235 1236 /** 1237 * Helper function: 1238 * Push the new media button receiver "near" the top of the PlayerRecord stack. 1239 * "Near the top" is defined as: 1240 * - at the top if the current PlayerRecord at the top is not playing 1241 * - below the entries at the top of the stack that correspond to the playing PlayerRecord 1242 * otherwise 1243 * Called synchronized on mPRStack 1244 * precondition: mediaIntent != null 1245 * @return true if the top of mPRStack was changed, false otherwise 1246 */ pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent, ComponentName target, IBinder token)1247 private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent, 1248 ComponentName target, IBinder token) { 1249 if (mPRStack.empty()) { 1250 mPRStack.push(new PlayerRecord(mediaIntent, target, token)); 1251 return true; 1252 } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) { 1253 // already at top of stack 1254 return false; 1255 } 1256 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(), 1257 mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) { 1258 return false; 1259 } 1260 PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes 1261 boolean topChanged = false; 1262 PlayerRecord prse = null; 1263 int lastPlayingIndex = mPRStack.size(); 1264 int inStackIndex = -1; 1265 try { 1266 // go through the stack from the top to figure out who's playing, and the position 1267 // of this media button receiver (note that it may not be in the stack) 1268 for (int index = mPRStack.size()-1; index >= 0; index--) { 1269 prse = mPRStack.elementAt(index); 1270 if (prse.isPlaybackActive()) { 1271 lastPlayingIndex = index; 1272 } 1273 if (prse.hasMatchingMediaButtonIntent(mediaIntent)) { 1274 inStackIndex = index; 1275 } 1276 } 1277 1278 if (inStackIndex == -1) { 1279 // is not in stack 1280 prse = new PlayerRecord(mediaIntent, target, token); 1281 // it's new so it's not playing (no RemoteControlClient to give a playstate), 1282 // therefore it goes after the ones with active playback 1283 mPRStack.add(lastPlayingIndex, prse); 1284 } else { 1285 // is in the stack 1286 if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1 1287 prse = mPRStack.elementAt(inStackIndex); 1288 // remove it from its old location in the stack 1289 mPRStack.removeElementAt(inStackIndex); 1290 if (prse.isPlaybackActive()) { 1291 // and put it at the top 1292 mPRStack.push(prse); 1293 } else { 1294 // and put it after the ones with active playback 1295 if (inStackIndex > lastPlayingIndex) { 1296 mPRStack.add(lastPlayingIndex, prse); 1297 } else { 1298 mPRStack.add(lastPlayingIndex - 1, prse); 1299 } 1300 } 1301 } 1302 } 1303 1304 } catch (ArrayIndexOutOfBoundsException e) { 1305 // not expected to happen, indicates improper concurrent modification or bad index 1306 Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex 1307 + " size=" + mPRStack.size() 1308 + " accessing media button stack", e); 1309 } 1310 1311 return (topChanged); 1312 } 1313 1314 /** 1315 * Helper function: 1316 * Remove the remote control receiver from the RC focus stack. 1317 * Called synchronized on mPRStack 1318 * precondition: pi != null 1319 */ removeMediaButtonReceiver_syncPrs(PendingIntent pi)1320 private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) { 1321 try { 1322 for (int index = mPRStack.size()-1; index >= 0; index--) { 1323 final PlayerRecord prse = mPRStack.elementAt(index); 1324 if (prse.hasMatchingMediaButtonIntent(pi)) { 1325 prse.destroy(); 1326 // ok to remove element while traversing the stack since we're leaving the loop 1327 mPRStack.removeElementAt(index); 1328 break; 1329 } 1330 } 1331 } catch (ArrayIndexOutOfBoundsException e) { 1332 // not expected to happen, indicates improper concurrent modification 1333 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); 1334 } 1335 } 1336 1337 /** 1338 * Helper function: 1339 * Called synchronized on mPRStack 1340 */ isCurrentRcController(PendingIntent pi)1341 private boolean isCurrentRcController(PendingIntent pi) { 1342 if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) { 1343 return true; 1344 } 1345 return false; 1346 } 1347 1348 //========================================================================================== 1349 // Remote control display / client 1350 //========================================================================================== 1351 /** 1352 * Update the remote control displays with the new "focused" client generation 1353 */ setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, PendingIntent newMediaIntent, boolean clearing)1354 private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, 1355 PendingIntent newMediaIntent, boolean clearing) { 1356 synchronized(mPRStack) { 1357 if (mRcDisplays.size() > 0) { 1358 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1359 while (displayIterator.hasNext()) { 1360 final DisplayInfoForServer di = displayIterator.next(); 1361 try { 1362 di.mRcDisplay.setCurrentClientId( 1363 newClientGeneration, newMediaIntent, clearing); 1364 } catch (RemoteException e) { 1365 Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); 1366 di.release(); 1367 displayIterator.remove(); 1368 } 1369 } 1370 } 1371 } 1372 } 1373 1374 /** 1375 * Update the remote control clients with the new "focused" client generation 1376 */ setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration)1377 private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { 1378 // (using an iterator on the stack so we can safely remove an entry if needed, 1379 // traversal order doesn't matter here as we update all entries) 1380 Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1381 while(stackIterator.hasNext()) { 1382 PlayerRecord se = stackIterator.next(); 1383 if ((se != null) && (se.getRcc() != null)) { 1384 try { 1385 se.getRcc().setCurrentClientGenerationId(newClientGeneration); 1386 } catch (RemoteException e) { 1387 Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); 1388 stackIterator.remove(); 1389 se.unlinkToRcClientDeath(); 1390 } 1391 } 1392 } 1393 } 1394 1395 /** 1396 * Update the displays and clients with the new "focused" client generation and name 1397 * @param newClientGeneration the new generation value matching a client update 1398 * @param newMediaIntent the media button event receiver associated with the client. 1399 * May be null, which implies there is no registered media button event receiver. 1400 * @param clearing true if the new client generation value maps to a remote control update 1401 * where the display should be cleared. 1402 */ setNewRcClient_syncRcsCurrc(int newClientGeneration, PendingIntent newMediaIntent, boolean clearing)1403 private void setNewRcClient_syncRcsCurrc(int newClientGeneration, 1404 PendingIntent newMediaIntent, boolean clearing) { 1405 // send the new valid client generation ID to all displays 1406 setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); 1407 // send the new valid client generation ID to all clients 1408 setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); 1409 } 1410 1411 /** 1412 * Called when processing MSG_RCDISPLAY_CLEAR event 1413 */ onRcDisplayClear()1414 private void onRcDisplayClear() { 1415 if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); 1416 1417 synchronized(mPRStack) { 1418 synchronized(mCurrentRcLock) { 1419 mCurrentRcClientGen++; 1420 // synchronously update the displays and clients with the new client generation 1421 setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, 1422 null /*newMediaIntent*/, true /*clearing*/); 1423 } 1424 } 1425 } 1426 1427 /** 1428 * Called when processing MSG_RCDISPLAY_UPDATE event 1429 */ onRcDisplayUpdate(PlayerRecord prse, int flags )1430 private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) { 1431 synchronized(mPRStack) { 1432 synchronized(mCurrentRcLock) { 1433 if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) { 1434 if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); 1435 1436 mCurrentRcClientGen++; 1437 // synchronously update the displays and clients with 1438 // the new client generation 1439 setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, 1440 prse.getMediaButtonIntent() /*newMediaIntent*/, 1441 false /*clearing*/); 1442 1443 // tell the current client that it needs to send info 1444 try { 1445 //TODO change name to informationRequestForAllDisplays() 1446 mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); 1447 } catch (RemoteException e) { 1448 Log.e(TAG, "Current valid remote client is dead: "+e); 1449 mCurrentRcClient = null; 1450 } 1451 } else { 1452 // the remote control display owner has changed between the 1453 // the message to update the display was sent, and the time it 1454 // gets to be processed (now) 1455 } 1456 } 1457 } 1458 } 1459 1460 /** 1461 * Called when processing MSG_RCDISPLAY_INIT_INFO event 1462 * Causes the current RemoteControlClient to send its info (metadata, playstate...) to 1463 * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE. 1464 */ onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h)1465 private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) { 1466 synchronized(mPRStack) { 1467 synchronized(mCurrentRcLock) { 1468 if (mCurrentRcClient != null) { 1469 if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); } 1470 try { 1471 // synchronously update the new RCD with the current client generation 1472 // and matching PendingIntent 1473 newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent, 1474 false); 1475 1476 // tell the current RCC that it needs to send info, but only to the new RCD 1477 try { 1478 mCurrentRcClient.informationRequestForDisplay(newRcd, w, h); 1479 } catch (RemoteException e) { 1480 Log.e(TAG, "Current valid remote client is dead: ", e); 1481 mCurrentRcClient = null; 1482 } 1483 } catch (RemoteException e) { 1484 Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e); 1485 } 1486 } 1487 } 1488 } 1489 } 1490 1491 /** 1492 * Helper function: 1493 * Called synchronized on mPRStack 1494 */ clearRemoteControlDisplay_syncPrs()1495 private void clearRemoteControlDisplay_syncPrs() { 1496 synchronized(mCurrentRcLock) { 1497 mCurrentRcClient = null; 1498 } 1499 // will cause onRcDisplayClear() to be called in AudioService's handler thread 1500 mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); 1501 } 1502 1503 /** 1504 * Helper function for code readability: only to be called from 1505 * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for 1506 * this method. 1507 * Preconditions: 1508 * - called synchronized on mPRStack 1509 * - mPRStack.isEmpty() is false 1510 */ updateRemoteControlDisplay_syncPrs(int infoChangedFlags)1511 private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) { 1512 PlayerRecord prse = mPRStack.peek(); 1513 int infoFlagsAboutToBeUsed = infoChangedFlags; 1514 // this is where we enforce opt-in for information display on the remote controls 1515 // with the new AudioManager.registerRemoteControlClient() API 1516 if (prse.getRcc() == null) { 1517 //Log.w(TAG, "Can't update remote control display with null remote control client"); 1518 clearRemoteControlDisplay_syncPrs(); 1519 return; 1520 } 1521 synchronized(mCurrentRcLock) { 1522 if (!prse.getRcc().equals(mCurrentRcClient)) { 1523 // new RC client, assume every type of information shall be queried 1524 infoFlagsAboutToBeUsed = RC_INFO_ALL; 1525 } 1526 mCurrentRcClient = prse.getRcc(); 1527 mCurrentRcClientIntent = prse.getMediaButtonIntent(); 1528 } 1529 // will cause onRcDisplayUpdate() to be called in AudioService's handler thread 1530 mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, 1531 infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) ); 1532 } 1533 1534 /** 1535 * Helper function: 1536 * Called synchronized on mPRStack 1537 * Check whether the remote control display should be updated, triggers the update if required 1538 * @param infoChangedFlags the flags corresponding to the remote control client information 1539 * that has changed, if applicable (checking for the update conditions might trigger a 1540 * clear, rather than an update event). 1541 */ checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags)1542 private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) { 1543 // determine whether the remote control display should be refreshed 1544 // if the player record stack is empty, there is nothing to display, so clear the RC display 1545 if (mPRStack.isEmpty()) { 1546 clearRemoteControlDisplay_syncPrs(); 1547 return; 1548 } 1549 1550 // this is where more rules for refresh go 1551 1552 // refresh conditions were verified: update the remote controls 1553 // ok to call: synchronized on mPRStack, mPRStack is not empty 1554 updateRemoteControlDisplay_syncPrs(infoChangedFlags); 1555 } 1556 1557 /** 1558 * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) 1559 * precondition: mediaIntent != null 1560 */ registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)1561 protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, 1562 IBinder token) { 1563 Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); 1564 1565 synchronized(mPRStack) { 1566 if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) { 1567 // new RC client, assume every type of information shall be queried 1568 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); 1569 } 1570 } 1571 } 1572 1573 /** 1574 * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) 1575 * precondition: mediaIntent != null, eventReceiver != null 1576 */ unregisterMediaButtonIntent(PendingIntent mediaIntent)1577 protected void unregisterMediaButtonIntent(PendingIntent mediaIntent) 1578 { 1579 Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); 1580 1581 synchronized(mPRStack) { 1582 boolean topOfStackWillChange = isCurrentRcController(mediaIntent); 1583 removeMediaButtonReceiver_syncPrs(mediaIntent); 1584 if (topOfStackWillChange) { 1585 // current RC client will change, assume every type of info needs to be queried 1586 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); 1587 } 1588 } 1589 } 1590 unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent)1591 protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) { 1592 mEventHandler.sendMessage( 1593 mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0, 1594 mediaIntent)); 1595 } 1596 1597 /** 1598 * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) 1599 * precondition: c != null 1600 */ registerMediaButtonEventReceiverForCalls(ComponentName c)1601 protected void registerMediaButtonEventReceiverForCalls(ComponentName c) { 1602 if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") 1603 != PackageManager.PERMISSION_GRANTED) { 1604 Log.e(TAG, "Invalid permissions to register media button receiver for calls"); 1605 return; 1606 } 1607 synchronized(mPRStack) { 1608 mMediaReceiverForCalls = c; 1609 } 1610 } 1611 1612 /** 1613 * see AudioManager.unregisterMediaButtonEventReceiverForCalls() 1614 */ unregisterMediaButtonEventReceiverForCalls()1615 protected void unregisterMediaButtonEventReceiverForCalls() { 1616 if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") 1617 != PackageManager.PERMISSION_GRANTED) { 1618 Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); 1619 return; 1620 } 1621 synchronized(mPRStack) { 1622 mMediaReceiverForCalls = null; 1623 } 1624 } 1625 1626 /** 1627 * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) 1628 * @return the unique ID of the PlayerRecord associated with the RemoteControlClient 1629 * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient 1630 * without modifying the RC stack, but while still causing the display to refresh (will 1631 * become blank as a result of this) 1632 */ registerRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient, String callingPackageName)1633 protected int registerRemoteControlClient(PendingIntent mediaIntent, 1634 IRemoteControlClient rcClient, String callingPackageName) { 1635 if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); 1636 int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; 1637 synchronized(mPRStack) { 1638 // store the new display information 1639 try { 1640 for (int index = mPRStack.size()-1; index >= 0; index--) { 1641 final PlayerRecord prse = mPRStack.elementAt(index); 1642 if(prse.hasMatchingMediaButtonIntent(mediaIntent)) { 1643 prse.resetControllerInfoForRcc(rcClient, callingPackageName, 1644 Binder.getCallingUid()); 1645 1646 if (rcClient == null) { 1647 break; 1648 } 1649 1650 rccId = prse.getRccId(); 1651 1652 // there is a new (non-null) client: 1653 // give the new client the displays (if any) 1654 if (mRcDisplays.size() > 0) { 1655 plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc()); 1656 } 1657 break; 1658 } 1659 }//for 1660 } catch (ArrayIndexOutOfBoundsException e) { 1661 // not expected to happen, indicates improper concurrent modification 1662 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); 1663 } 1664 1665 // if the eventReceiver is at the top of the stack 1666 // then check for potential refresh of the remote controls 1667 if (isCurrentRcController(mediaIntent)) { 1668 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); 1669 } 1670 }//synchronized(mPRStack) 1671 return rccId; 1672 } 1673 1674 /** 1675 * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) 1676 * rcClient is guaranteed non-null 1677 */ unregisterRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient)1678 protected void unregisterRemoteControlClient(PendingIntent mediaIntent, 1679 IRemoteControlClient rcClient) { 1680 if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); 1681 synchronized(mPRStack) { 1682 boolean topRccChange = false; 1683 try { 1684 for (int index = mPRStack.size()-1; index >= 0; index--) { 1685 final PlayerRecord prse = mPRStack.elementAt(index); 1686 if ((prse.hasMatchingMediaButtonIntent(mediaIntent)) 1687 && rcClient.equals(prse.getRcc())) { 1688 // we found the IRemoteControlClient to unregister 1689 prse.resetControllerInfoForNoRcc(); 1690 topRccChange = (index == mPRStack.size()-1); 1691 // there can only be one matching RCC in the RC stack, we're done 1692 break; 1693 } 1694 } 1695 } catch (ArrayIndexOutOfBoundsException e) { 1696 // not expected to happen, indicates improper concurrent modification 1697 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); 1698 } 1699 if (topRccChange) { 1700 // no more RCC for the RCD, check for potential refresh of the remote controls 1701 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); 1702 } 1703 } 1704 } 1705 1706 1707 /** 1708 * A class to encapsulate all the information about a remote control display. 1709 * After instanciation, init() must always be called before the object is added in the list 1710 * of displays. 1711 * Before being removed from the list of displays, release() must always be called (otherwise 1712 * it will leak death handlers). 1713 */ 1714 private class DisplayInfoForServer implements IBinder.DeathRecipient { 1715 /** may never be null */ 1716 private final IRemoteControlDisplay mRcDisplay; 1717 private final IBinder mRcDisplayBinder; 1718 private int mArtworkExpectedWidth = -1; 1719 private int mArtworkExpectedHeight = -1; 1720 private boolean mWantsPositionSync = false; 1721 private ComponentName mClientNotifListComp; 1722 private boolean mEnabled = true; 1723 DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h)1724 public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { 1725 if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); 1726 mRcDisplay = rcd; 1727 mRcDisplayBinder = rcd.asBinder(); 1728 mArtworkExpectedWidth = w; 1729 mArtworkExpectedHeight = h; 1730 } 1731 init()1732 public boolean init() { 1733 try { 1734 mRcDisplayBinder.linkToDeath(this, 0); 1735 } catch (RemoteException e) { 1736 // remote control display is DOA, disqualify it 1737 Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); 1738 return false; 1739 } 1740 return true; 1741 } 1742 release()1743 public void release() { 1744 try { 1745 mRcDisplayBinder.unlinkToDeath(this, 0); 1746 } catch (java.util.NoSuchElementException e) { 1747 // not much we can do here, the display should have been unregistered anyway 1748 Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); 1749 } 1750 } 1751 binderDied()1752 public void binderDied() { 1753 synchronized(mPRStack) { 1754 Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); 1755 // remove the display from the list 1756 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1757 while (displayIterator.hasNext()) { 1758 final DisplayInfoForServer di = displayIterator.next(); 1759 if (di.mRcDisplay == mRcDisplay) { 1760 if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); 1761 displayIterator.remove(); 1762 return; 1763 } 1764 } 1765 } 1766 } 1767 } 1768 1769 /** 1770 * The remote control displays. 1771 * Access synchronized on mPRStack 1772 */ 1773 private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1); 1774 1775 /** 1776 * Plug each registered display into the specified client 1777 * @param rcc, guaranteed non null 1778 */ plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc)1779 private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) { 1780 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1781 while (displayIterator.hasNext()) { 1782 final DisplayInfoForServer di = displayIterator.next(); 1783 try { 1784 rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, 1785 di.mArtworkExpectedHeight); 1786 if (di.mWantsPositionSync) { 1787 rcc.setWantsSyncForDisplay(di.mRcDisplay, true); 1788 } 1789 } catch (RemoteException e) { 1790 Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); 1791 } 1792 } 1793 } 1794 enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, boolean enabled)1795 private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, 1796 boolean enabled) { 1797 // let all the remote control clients know whether the given display is enabled 1798 // (so the remote control stack traversal order doesn't matter). 1799 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1800 while(stackIterator.hasNext()) { 1801 PlayerRecord prse = stackIterator.next(); 1802 if(prse.getRcc() != null) { 1803 try { 1804 prse.getRcc().enableRemoteControlDisplay(rcd, enabled); 1805 } catch (RemoteException e) { 1806 Log.e(TAG, "Error connecting RCD to client: ", e); 1807 } 1808 } 1809 } 1810 } 1811 1812 /** 1813 * Is the remote control display interface already registered 1814 * @param rcd 1815 * @return true if the IRemoteControlDisplay is already in the list of displays 1816 */ rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd)1817 private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { 1818 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1819 while (displayIterator.hasNext()) { 1820 final DisplayInfoForServer di = displayIterator.next(); 1821 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1822 return true; 1823 } 1824 } 1825 return false; 1826 } 1827 1828 /** 1829 * Register an IRemoteControlDisplay. 1830 * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient 1831 * at the top of the stack to update the new display with its information. 1832 * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) 1833 * @param rcd the IRemoteControlDisplay to register. No effect if null. 1834 * @param w the maximum width of the expected bitmap. Negative or zero values indicate this 1835 * display doesn't need to receive artwork. 1836 * @param h the maximum height of the expected bitmap. Negative or zero values indicate this 1837 * display doesn't need to receive artwork. 1838 * @param listenerComp the component for the listener interface, may be null if it's not needed 1839 * to verify it belongs to one of the enabled notification listeners 1840 */ registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, ComponentName listenerComp)1841 private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, 1842 ComponentName listenerComp) { 1843 if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); 1844 synchronized(mAudioFocusLock) { 1845 synchronized(mPRStack) { 1846 if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { 1847 return; 1848 } 1849 DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); 1850 di.mEnabled = true; 1851 di.mClientNotifListComp = listenerComp; 1852 if (!di.init()) { 1853 if (DEBUG_RC) Log.e(TAG, " error registering RCD"); 1854 return; 1855 } 1856 // add RCD to list of displays 1857 mRcDisplays.add(di); 1858 1859 // let all the remote control clients know there is a new display (so the remote 1860 // control stack traversal order doesn't matter). 1861 Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1862 while(stackIterator.hasNext()) { 1863 PlayerRecord prse = stackIterator.next(); 1864 if(prse.getRcc() != null) { 1865 try { 1866 prse.getRcc().plugRemoteControlDisplay(rcd, w, h); 1867 } catch (RemoteException e) { 1868 Log.e(TAG, "Error connecting RCD to client: ", e); 1869 } 1870 } 1871 } 1872 1873 // we have a new display, of which all the clients are now aware: have it be 1874 // initialized wih the current gen ID and the current client info, do not 1875 // reset the information for the other (existing) displays 1876 sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, 1877 w /*arg1*/, h /*arg2*/, 1878 rcd /*obj*/, 0/*delay*/); 1879 } 1880 } 1881 } 1882 1883 /** 1884 * Unregister an IRemoteControlDisplay. 1885 * No effect if the IRemoteControlDisplay hasn't been successfully registered. 1886 * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) 1887 * @param rcd the IRemoteControlDisplay to unregister. No effect if null. 1888 */ unregisterRemoteControlDisplay(IRemoteControlDisplay rcd)1889 protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { 1890 if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); 1891 synchronized(mPRStack) { 1892 if (rcd == null) { 1893 return; 1894 } 1895 1896 boolean displayWasPluggedIn = false; 1897 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1898 while (displayIterator.hasNext() && !displayWasPluggedIn) { 1899 final DisplayInfoForServer di = displayIterator.next(); 1900 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1901 displayWasPluggedIn = true; 1902 di.release(); 1903 displayIterator.remove(); 1904 } 1905 } 1906 1907 if (displayWasPluggedIn) { 1908 // disconnect this remote control display from all the clients, so the remote 1909 // control stack traversal order doesn't matter 1910 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1911 while(stackIterator.hasNext()) { 1912 final PlayerRecord prse = stackIterator.next(); 1913 if(prse.getRcc() != null) { 1914 try { 1915 prse.getRcc().unplugRemoteControlDisplay(rcd); 1916 } catch (RemoteException e) { 1917 Log.e(TAG, "Error disconnecting remote control display to client: ", e); 1918 } 1919 } 1920 } 1921 } else { 1922 if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); 1923 } 1924 } 1925 } 1926 1927 /** 1928 * Update the size of the artwork used by an IRemoteControlDisplay. 1929 * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) 1930 * @param rcd the IRemoteControlDisplay with the new artwork size requirement 1931 * @param w the maximum width of the expected bitmap. Negative or zero values indicate this 1932 * display doesn't need to receive artwork. 1933 * @param h the maximum height of the expected bitmap. Negative or zero values indicate this 1934 * display doesn't need to receive artwork. 1935 */ remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h)1936 protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { 1937 synchronized(mPRStack) { 1938 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1939 boolean artworkSizeUpdate = false; 1940 while (displayIterator.hasNext() && !artworkSizeUpdate) { 1941 final DisplayInfoForServer di = displayIterator.next(); 1942 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1943 if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { 1944 di.mArtworkExpectedWidth = w; 1945 di.mArtworkExpectedHeight = h; 1946 artworkSizeUpdate = true; 1947 } 1948 } 1949 } 1950 if (artworkSizeUpdate) { 1951 // RCD is currently plugged in and its artwork size has changed, notify all RCCs, 1952 // stack traversal order doesn't matter 1953 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 1954 while(stackIterator.hasNext()) { 1955 final PlayerRecord prse = stackIterator.next(); 1956 if(prse.getRcc() != null) { 1957 try { 1958 prse.getRcc().setBitmapSizeForDisplay(rcd, w, h); 1959 } catch (RemoteException e) { 1960 Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); 1961 } 1962 } 1963 } 1964 } 1965 } 1966 } 1967 1968 /** 1969 * Controls whether a remote control display needs periodic checks of the RemoteControlClient 1970 * playback position to verify that the estimated position has not drifted from the actual 1971 * position. By default the check is not performed. 1972 * The IRemoteControlDisplay must have been previously registered for this to have any effect. 1973 * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled 1974 * or disabled. Not null. 1975 * @param wantsSync if true, RemoteControlClient instances which expose their playback position 1976 * to the framework will regularly compare the estimated playback position with the actual 1977 * position, and will update the IRemoteControlDisplay implementation whenever a drift is 1978 * detected. 1979 */ remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, boolean wantsSync)1980 protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, 1981 boolean wantsSync) { 1982 synchronized(mPRStack) { 1983 boolean rcdRegistered = false; 1984 // store the information about this display 1985 // (display stack traversal order doesn't matter). 1986 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); 1987 while (displayIterator.hasNext()) { 1988 final DisplayInfoForServer di = displayIterator.next(); 1989 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { 1990 di.mWantsPositionSync = wantsSync; 1991 rcdRegistered = true; 1992 break; 1993 } 1994 } 1995 if (!rcdRegistered) { 1996 return; 1997 } 1998 // notify all current RemoteControlClients 1999 // (stack traversal order doesn't matter as we notify all RCCs) 2000 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); 2001 while (stackIterator.hasNext()) { 2002 final PlayerRecord prse = stackIterator.next(); 2003 if (prse.getRcc() != null) { 2004 try { 2005 prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync); 2006 } catch (RemoteException e) { 2007 Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); 2008 } 2009 } 2010 } 2011 } 2012 } 2013 2014 // handler for MSG_RCC_NEW_VOLUME_OBS onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo)2015 private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { 2016 synchronized(mPRStack) { 2017 // The stack traversal order doesn't matter because there is only one stack entry 2018 // with this RCC ID, but the matching ID is more likely at the top of the stack, so 2019 // start iterating from the top. 2020 try { 2021 for (int index = mPRStack.size()-1; index >= 0; index--) { 2022 final PlayerRecord prse = mPRStack.elementAt(index); 2023 if (prse.getRccId() == rccId) { 2024 prse.mRemoteVolumeObs = rvo; 2025 break; 2026 } 2027 } 2028 } catch (ArrayIndexOutOfBoundsException e) { 2029 // not expected to happen, indicates improper concurrent modification 2030 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); 2031 } 2032 } 2033 } 2034 2035 /** 2036 * Checks if a remote client is active on the supplied stream type. Update the remote stream 2037 * volume state if found and playing 2038 * @param streamType 2039 * @return false if no remote playing is currently playing 2040 */ checkUpdateRemoteStateIfActive(int streamType)2041 protected boolean checkUpdateRemoteStateIfActive(int streamType) { 2042 synchronized(mPRStack) { 2043 // iterating from top of stack as active playback is more likely on entries at the top 2044 try { 2045 for (int index = mPRStack.size()-1; index >= 0; index--) { 2046 final PlayerRecord prse = mPRStack.elementAt(index); 2047 if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) 2048 && isPlaystateActive(prse.mPlaybackState.mState) 2049 && (prse.mPlaybackStream == streamType)) { 2050 if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType 2051 + ", vol =" + prse.mPlaybackVolume); 2052 synchronized (mMainRemote) { 2053 mMainRemote.mRccId = prse.getRccId(); 2054 mMainRemote.mVolume = prse.mPlaybackVolume; 2055 mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax; 2056 mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling; 2057 mMainRemoteIsActive = true; 2058 } 2059 return true; 2060 } 2061 } 2062 } catch (ArrayIndexOutOfBoundsException e) { 2063 // not expected to happen, indicates improper concurrent modification 2064 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); 2065 } 2066 } 2067 synchronized (mMainRemote) { 2068 mMainRemoteIsActive = false; 2069 } 2070 return false; 2071 } 2072 2073 /** 2074 * Returns true if the given playback state is considered "active", i.e. it describes a state 2075 * where playback is happening, or about to 2076 * @param playState the playback state to evaluate 2077 * @return true if active, false otherwise (inactive or unknown) 2078 */ isPlaystateActive(int playState)2079 protected static boolean isPlaystateActive(int playState) { 2080 switch (playState) { 2081 case RemoteControlClient.PLAYSTATE_PLAYING: 2082 case RemoteControlClient.PLAYSTATE_BUFFERING: 2083 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: 2084 case RemoteControlClient.PLAYSTATE_REWINDING: 2085 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: 2086 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: 2087 return true; 2088 default: 2089 return false; 2090 } 2091 } 2092 sendVolumeUpdateToRemote(int rccId, int direction)2093 private void sendVolumeUpdateToRemote(int rccId, int direction) { 2094 if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } 2095 if (direction == 0) { 2096 // only handling discrete events 2097 return; 2098 } 2099 IRemoteVolumeObserver rvo = null; 2100 synchronized (mPRStack) { 2101 // The stack traversal order doesn't matter because there is only one stack entry 2102 // with this RCC ID, but the matching ID is more likely at the top of the stack, so 2103 // start iterating from the top. 2104 try { 2105 for (int index = mPRStack.size()-1; index >= 0; index--) { 2106 final PlayerRecord prse = mPRStack.elementAt(index); 2107 //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? 2108 if (prse.getRccId() == rccId) { 2109 rvo = prse.mRemoteVolumeObs; 2110 break; 2111 } 2112 } 2113 } catch (ArrayIndexOutOfBoundsException e) { 2114 // not expected to happen, indicates improper concurrent modification 2115 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); 2116 } 2117 } 2118 if (rvo != null) { 2119 try { 2120 rvo.dispatchRemoteVolumeUpdate(direction, -1); 2121 } catch (RemoteException e) { 2122 Log.e(TAG, "Error dispatching relative volume update", e); 2123 } 2124 } 2125 } 2126 getRemoteStreamMaxVolume()2127 protected int getRemoteStreamMaxVolume() { 2128 synchronized (mMainRemote) { 2129 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { 2130 return 0; 2131 } 2132 return mMainRemote.mVolumeMax; 2133 } 2134 } 2135 getRemoteStreamVolume()2136 protected int getRemoteStreamVolume() { 2137 synchronized (mMainRemote) { 2138 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { 2139 return 0; 2140 } 2141 return mMainRemote.mVolume; 2142 } 2143 } 2144 setRemoteStreamVolume(int vol)2145 protected void setRemoteStreamVolume(int vol) { 2146 if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } 2147 int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; 2148 synchronized (mMainRemote) { 2149 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { 2150 return; 2151 } 2152 rccId = mMainRemote.mRccId; 2153 } 2154 IRemoteVolumeObserver rvo = null; 2155 synchronized (mPRStack) { 2156 // The stack traversal order doesn't matter because there is only one stack entry 2157 // with this RCC ID, but the matching ID is more likely at the top of the stack, so 2158 // start iterating from the top. 2159 try { 2160 for (int index = mPRStack.size()-1; index >= 0; index--) { 2161 final PlayerRecord prse = mPRStack.elementAt(index); 2162 //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? 2163 if (prse.getRccId() == rccId) { 2164 rvo = prse.mRemoteVolumeObs; 2165 break; 2166 } 2167 } 2168 } catch (ArrayIndexOutOfBoundsException e) { 2169 // not expected to happen, indicates improper concurrent modification 2170 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); 2171 } 2172 } 2173 if (rvo != null) { 2174 try { 2175 rvo.dispatchRemoteVolumeUpdate(0, vol); 2176 } catch (RemoteException e) { 2177 Log.e(TAG, "Error dispatching absolute volume update", e); 2178 } 2179 } 2180 } 2181 2182 /** 2183 * Call to make AudioService reevaluate whether it's in a mode where remote players should 2184 * have their volume controlled. In this implementation this is only to reset whether 2185 * VolumePanel should display remote volumes 2186 */ postReevaluateRemote()2187 protected void postReevaluateRemote() { 2188 sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); 2189 } 2190 onReevaluateRemote()2191 private void onReevaluateRemote() { 2192 // TODO This was used to notify VolumePanel if there was remote playback 2193 // in the stack. This is now in MediaSessionService. More code should be 2194 // removed. 2195 } 2196 2197 } 2198