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