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