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