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