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