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