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