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