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