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