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