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