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