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