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 MediaFocusControl(Looper looper, Context cntxt, AudioService.VolumeController volumeCtrl, AudioService as)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 dump(PrintWriter pw)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 NotificationListenerObserver()147 NotificationListenerObserver() { 148 super(mEventHandler); 149 mContentResolver.registerContentObserver(Settings.Secure.getUriFor( 150 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); 151 } 152 153 @Override onChange(boolean selfChange, Uri uri)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 */ checkRcdRegistrationAuthorization(ComponentName listenerComp)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 registerRemoteController(IRemoteControlDisplay rcd, int w, int h, ComponentName listenerComp)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 registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h)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 postReevaluateRemoteControlDisplays()246 private void postReevaluateRemoteControlDisplays() { 247 sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0); 248 } 249 onReevaluateRemoteControlDisplays()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 */ isComponentInStringArray(ComponentName comp, String[] enabledArray)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 sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay)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 { MediaEventHandler(Looper looper)360 MediaEventHandler(Looper looper) { 361 super(looper); 362 } 363 364 @Override handleMessage(Message msg)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 */ discardAudioFocusOwner()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 */ notifyTopOfAudioFocusStack()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 */ propagateFocusLossFromGain_syncAf(int focusGain)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 */ dumpFocusStack(PrintWriter pw)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 */ removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers)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 */ removeFocusStackEntryForClient(IBinder cb)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 */ canReassignAudioFocus()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 isLockedFocusOwner(FocusRequester fr)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 */ pushBelowLockedFocusOwners(FocusRequester nfr)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 AudioFocusDeathHandler(IBinder cb)614 AudioFocusDeathHandler(IBinder cb) { 615 mCb = cb; 616 } 617 binderDied()618 public void binderDied() { 619 synchronized(mAudioFocusLock) { 620 Log.w(TAG, " AudioFocus audio focus client died"); 621 removeFocusStackEntryForClient(mCb); 622 } 623 } 624 getBinder()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 setDuckingInExtPolicyAvailable(boolean available)638 protected void setDuckingInExtPolicyAvailable(boolean available) { 639 mNotifyFocusOwnerOnDuck = !available; 640 } 641 mustNotifyFocusOwnerOnDuck()642 boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; } 643 644 private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>(); 645 addFocusFollower(IAudioPolicyCallback ff)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 removeFocusFollower(IAudioPolicyCallback ff)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 */ notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb)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 */ notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)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 */ notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)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 getCurrentAudioFocus()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) */ requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags)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 * */ abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa)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 unregisterAudioFocusClient(String clientId)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 */ dispatchMediaKeyEvent(KeyEvent keyEvent)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 */ dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent)886 protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { 887 filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); 888 } 889 filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock)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 */ dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock)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 */ dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock)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 */ filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock)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 sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock)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 isValidMediaKeyEvent(KeyEvent keyEvent)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 */ isValidVoiceInputKeyCode(int keyCode)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 */ startVoiceBasedInteractions(boolean needWakeLock)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 onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras)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 */ dumpRCStack(PrintWriter pw)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 */ dumpRCCStack(PrintWriter pw)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 */ dumpRCDList(PrintWriter pw)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 */ pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent, ComponentName target, IBinder token)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 */ removeMediaButtonReceiver_syncPrs(PendingIntent pi)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 */ isCurrentRcController(PendingIntent pi)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 */ setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, PendingIntent newMediaIntent, boolean clearing)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 */ setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration)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 */ setNewRcClient_syncRcsCurrc(int newClientGeneration, PendingIntent newMediaIntent, boolean clearing)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 */ onRcDisplayClear()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 */ onRcDisplayUpdate(PlayerRecord prse, int flags )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 */ onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h)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 */ clearRemoteControlDisplay_syncPrs()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 */ updateRemoteControlDisplay_syncPrs(int infoChangedFlags)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 */ checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags)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 */ registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)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 */ unregisterMediaButtonIntent(PendingIntent mediaIntent)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 unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent)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 */ registerMediaButtonEventReceiverForCalls(ComponentName c)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 */ unregisterMediaButtonEventReceiverForCalls()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 */ registerRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient, String callingPackageName)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 */ unregisterRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient)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 DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h)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 init()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 release()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 binderDied()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 */ plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc)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 enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, boolean enabled)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 */ rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd)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 */ registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, ComponentName listenerComp)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 */ unregisterRemoteControlDisplay(IRemoteControlDisplay rcd)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 */ remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h)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 */ remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, boolean wantsSync)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 onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo)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 */ checkUpdateRemoteStateIfActive(int streamType)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 */ isPlaystateActive(int playState)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 sendVolumeUpdateToRemote(int rccId, int direction)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 getRemoteStreamMaxVolume()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 getRemoteStreamVolume()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 setRemoteStreamVolume(int vol)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 */ postReevaluateRemote()2228 protected void postReevaluateRemote() { 2229 sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); 2230 } 2231 onReevaluateRemote()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