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