MediaSessionRecord.java revision 7c82ced4fc5b66c09a19eed9a5499039530142fb
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.media; 18 19import android.app.PendingIntent; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.pm.ParceledListSlice; 24import android.media.AudioManager; 25import android.media.AudioManagerInternal; 26import android.media.AudioSystem; 27import android.media.MediaDescription; 28import android.media.MediaMetadata; 29import android.media.Rating; 30import android.media.VolumeProvider; 31import android.media.session.ISession; 32import android.media.session.ISessionCallback; 33import android.media.session.ISessionController; 34import android.media.session.ISessionControllerCallback; 35import android.media.session.MediaController; 36import android.media.session.MediaController.PlaybackInfo; 37import android.media.session.MediaSession; 38import android.media.session.MediaSessionManager; 39import android.media.session.ParcelableVolumeInfo; 40import android.media.session.PlaybackState; 41import android.media.AudioAttributes; 42import android.net.Uri; 43import android.os.Binder; 44import android.os.Bundle; 45import android.os.DeadObjectException; 46import android.os.Handler; 47import android.os.IBinder; 48import android.os.Looper; 49import android.os.Message; 50import android.os.RemoteException; 51import android.os.ResultReceiver; 52import android.os.SystemClock; 53import android.util.Log; 54import android.util.Slog; 55import android.view.KeyEvent; 56 57import com.android.server.LocalServices; 58 59import java.io.PrintWriter; 60import java.util.ArrayList; 61import java.util.UUID; 62 63/** 64 * This is the system implementation of a Session. Apps will interact with the 65 * MediaSession wrapper class instead. 66 */ 67public class MediaSessionRecord implements IBinder.DeathRecipient { 68 private static final String TAG = "MediaSessionRecord"; 69 private static final boolean DEBUG = false; 70 71 /** 72 * The length of time a session will still be considered active after 73 * pausing in ms. 74 */ 75 private static final int ACTIVE_BUFFER = 30000; 76 77 /** 78 * The amount of time we'll send an assumed volume after the last volume 79 * command before reverting to the last reported volume. 80 */ 81 private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000; 82 83 private final MessageHandler mHandler; 84 85 private final int mOwnerPid; 86 private final int mOwnerUid; 87 private final int mUserId; 88 private final String mPackageName; 89 private final String mTag; 90 private final ControllerStub mController; 91 private final SessionStub mSession; 92 private final SessionCb mSessionCb; 93 private final MediaSessionService mService; 94 private final boolean mUseMasterVolume; 95 96 private final IBinder mICallback = new Binder(); 97 private final Object mLock = new Object(); 98 private final ArrayList<ISessionControllerCallback> mControllerCallbacks = 99 new ArrayList<ISessionControllerCallback>(); 100 101 private long mFlags; 102 private PendingIntent mMediaButtonReceiver; 103 private PendingIntent mLaunchIntent; 104 105 // TransportPerformer fields 106 107 private Bundle mExtras; 108 private MediaMetadata mMetadata; 109 private PlaybackState mPlaybackState; 110 private ParceledListSlice mQueue; 111 private CharSequence mQueueTitle; 112 private int mRatingType; 113 private long mLastActiveTime; 114 // End TransportPerformer fields 115 116 // Volume handling fields 117 private AudioAttributes mAudioAttrs; 118 private AudioManager mAudioManager; 119 private AudioManagerInternal mAudioManagerInternal; 120 private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; 121 private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; 122 private int mMaxVolume = 0; 123 private int mCurrentVolume = 0; 124 private int mOptimisticVolume = -1; 125 // End volume handling fields 126 127 private boolean mIsActive = false; 128 private boolean mDestroyed = false; 129 130 public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, 131 ISessionCallback cb, String tag, MediaSessionService service, Handler handler) { 132 mOwnerPid = ownerPid; 133 mOwnerUid = ownerUid; 134 mUserId = userId; 135 mPackageName = ownerPackageName; 136 mTag = tag; 137 mController = new ControllerStub(); 138 mSession = new SessionStub(); 139 mSessionCb = new SessionCb(cb); 140 mService = service; 141 mHandler = new MessageHandler(handler.getLooper()); 142 mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE); 143 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); 144 mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); 145 mUseMasterVolume = service.getContext().getResources().getBoolean( 146 com.android.internal.R.bool.config_useMasterVolume); 147 } 148 149 /** 150 * Get the binder for the {@link MediaSession}. 151 * 152 * @return The session binder apps talk to. 153 */ 154 public ISession getSessionBinder() { 155 return mSession; 156 } 157 158 /** 159 * Get the binder for the {@link MediaController}. 160 * 161 * @return The controller binder apps talk to. 162 */ 163 public ISessionController getControllerBinder() { 164 return mController; 165 } 166 167 /** 168 * Get the info for this session. 169 * 170 * @return Info that identifies this session. 171 */ 172 public String getPackageName() { 173 return mPackageName; 174 } 175 176 /** 177 * Get the tag for the session. 178 * 179 * @return The session's tag. 180 */ 181 public String getTag() { 182 return mTag; 183 } 184 185 /** 186 * Get the intent the app set for their media button receiver. 187 * 188 * @return The pending intent set by the app or null. 189 */ 190 public PendingIntent getMediaButtonReceiver() { 191 return mMediaButtonReceiver; 192 } 193 194 /** 195 * Get this session's flags. 196 * 197 * @return The flags for this session. 198 */ 199 public long getFlags() { 200 return mFlags; 201 } 202 203 /** 204 * Check if this session has the specified flag. 205 * 206 * @param flag The flag to check. 207 * @return True if this session has that flag set, false otherwise. 208 */ 209 public boolean hasFlag(int flag) { 210 return (mFlags & flag) != 0; 211 } 212 213 /** 214 * Get the user id this session was created for. 215 * 216 * @return The user id for this session. 217 */ 218 public int getUserId() { 219 return mUserId; 220 } 221 222 /** 223 * Check if this session has system priorty and should receive media buttons 224 * before any other sessions. 225 * 226 * @return True if this is a system priority session, false otherwise 227 */ 228 public boolean isSystemPriority() { 229 return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; 230 } 231 232 /** 233 * Send a volume adjustment to the session owner. Direction must be one of 234 * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, 235 * {@link AudioManager#ADJUST_SAME}. 236 * 237 * @param direction The direction to adjust volume in. 238 * @param flags Any of the flags from {@link AudioManager}. 239 * @param packageName The package that made the original volume request. 240 * @param uid The uid that made the original volume request. 241 * @param useSuggested True to use adjustSuggestedStreamVolume instead of 242 * adjustStreamVolume. 243 */ 244 public void adjustVolume(int direction, int flags, String packageName, int uid, 245 boolean useSuggested) { 246 int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND; 247 if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { 248 flags &= ~AudioManager.FLAG_PLAY_SOUND; 249 } 250 boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE; 251 if (direction > 1) { 252 direction = 1; 253 } else if (direction < -1) { 254 direction = -1; 255 } 256 if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { 257 if (mUseMasterVolume) { 258 // If this device only uses master volume and playback is local 259 // just adjust the master volume and return. 260 if (isMute) { 261 mAudioManagerInternal.setMasterMuteForUid(!mAudioManager.isMasterMute(), 262 flags, packageName, mICallback, uid); 263 } else { 264 mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, 265 uid); 266 } 267 return; 268 } 269 int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); 270 if (useSuggested) { 271 if (AudioSystem.isStreamActive(stream, 0)) { 272 if (isMute) { 273 mAudioManager.setStreamMute(stream, !mAudioManager.isStreamMute(stream)); 274 } else { 275 mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, 276 flags, packageName, uid); 277 } 278 } else { 279 flags |= previousFlagPlaySound; 280 if (isMute) { 281 mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE, 282 !mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE)); 283 } else { 284 mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( 285 AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName, 286 uid); 287 } 288 } 289 } else { 290 if (isMute) { 291 mAudioManager.setStreamMute(stream, !mAudioManager.isStreamMute(stream)); 292 } else { 293 mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, 294 packageName, uid); 295 } 296 } 297 } else { 298 if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { 299 // Nothing to do, the volume cannot be changed 300 return; 301 } 302 if (isMute) { 303 Log.w(TAG, "Muting remote playback is not supported"); 304 return; 305 } 306 mSessionCb.adjustVolume(direction); 307 308 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); 309 mOptimisticVolume = volumeBefore + direction; 310 mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume)); 311 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); 312 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); 313 if (volumeBefore != mOptimisticVolume) { 314 pushVolumeUpdate(); 315 } 316 317 if (DEBUG) { 318 Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is " 319 + mMaxVolume); 320 } 321 } 322 } 323 324 public void setVolumeTo(int value, int flags, String packageName, int uid) { 325 if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { 326 int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); 327 mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid); 328 } else { 329 if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) { 330 // Nothing to do. The volume can't be set directly. 331 return; 332 } 333 value = Math.max(0, Math.min(value, mMaxVolume)); 334 mSessionCb.setVolumeTo(value); 335 336 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); 337 mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume)); 338 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); 339 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); 340 if (volumeBefore != mOptimisticVolume) { 341 pushVolumeUpdate(); 342 } 343 344 if (DEBUG) { 345 Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is " 346 + mMaxVolume); 347 } 348 } 349 } 350 351 /** 352 * Check if this session has been set to active by the app. 353 * 354 * @return True if the session is active, false otherwise. 355 */ 356 public boolean isActive() { 357 return mIsActive && !mDestroyed; 358 } 359 360 /** 361 * Check if the session is currently performing playback. This will also 362 * return true if the session was recently paused. 363 * 364 * @param includeRecentlyActive True if playback that was recently paused 365 * should count, false if it shouldn't. 366 * @return True if the session is performing playback, false otherwise. 367 */ 368 public boolean isPlaybackActive(boolean includeRecentlyActive) { 369 int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); 370 if (MediaSession.isActiveState(state)) { 371 return true; 372 } 373 if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) { 374 long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; 375 if (inactiveTime < ACTIVE_BUFFER) { 376 return true; 377 } 378 } 379 return false; 380 } 381 382 /** 383 * Get the type of playback, either local or remote. 384 * 385 * @return The current type of playback. 386 */ 387 public int getPlaybackType() { 388 return mVolumeType; 389 } 390 391 /** 392 * Get the local audio stream being used. Only valid if playback type is 393 * local. 394 * 395 * @return The audio stream the session is using. 396 */ 397 public AudioAttributes getAudioAttributes() { 398 return mAudioAttrs; 399 } 400 401 /** 402 * Get the type of volume control. Only valid if playback type is remote. 403 * 404 * @return The volume control type being used. 405 */ 406 public int getVolumeControl() { 407 return mVolumeControlType; 408 } 409 410 /** 411 * Get the max volume that can be set. Only valid if playback type is 412 * remote. 413 * 414 * @return The max volume that can be set. 415 */ 416 public int getMaxVolume() { 417 return mMaxVolume; 418 } 419 420 /** 421 * Get the current volume for this session. Only valid if playback type is 422 * remote. 423 * 424 * @return The current volume of the remote playback. 425 */ 426 public int getCurrentVolume() { 427 return mCurrentVolume; 428 } 429 430 /** 431 * Get the volume we'd like it to be set to. This is only valid for a short 432 * while after a call to adjust or set volume. 433 * 434 * @return The current optimistic volume or -1. 435 */ 436 public int getOptimisticVolume() { 437 return mOptimisticVolume; 438 } 439 440 public boolean isTransportControlEnabled() { 441 return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 442 } 443 444 @Override 445 public void binderDied() { 446 mService.sessionDied(this); 447 } 448 449 /** 450 * Finish cleaning up this session, including disconnecting if connected and 451 * removing the death observer from the callback binder. 452 */ 453 public void onDestroy() { 454 synchronized (mLock) { 455 if (mDestroyed) { 456 return; 457 } 458 mDestroyed = true; 459 mHandler.post(MessageHandler.MSG_DESTROYED); 460 } 461 } 462 463 public ISessionCallback getCallback() { 464 return mSessionCb.mCb; 465 } 466 467 public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) { 468 mSessionCb.sendMediaButton(ke, sequenceId, cb); 469 } 470 471 public void dump(PrintWriter pw, String prefix) { 472 pw.println(prefix + mTag + " " + this); 473 474 final String indent = prefix + " "; 475 pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid 476 + ", userId=" + mUserId); 477 pw.println(indent + "package=" + mPackageName); 478 pw.println(indent + "launchIntent=" + mLaunchIntent); 479 pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver); 480 pw.println(indent + "active=" + mIsActive); 481 pw.println(indent + "flags=" + mFlags); 482 pw.println(indent + "rating type=" + mRatingType); 483 pw.println(indent + "controllers: " + mControllerCallbacks.size()); 484 pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString())); 485 pw.println(indent + "audioAttrs=" + mAudioAttrs); 486 pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType 487 + ", max=" + mMaxVolume + ", current=" + mCurrentVolume); 488 pw.println(indent + "metadata:" + getShortMetadataString()); 489 pw.println(indent + "queueTitle=" + mQueueTitle + ", size=" 490 + (mQueue == null ? 0 : mQueue.getList().size())); 491 } 492 493 @Override 494 public String toString() { 495 return mPackageName + "/" + mTag; 496 } 497 498 private String getShortMetadataString() { 499 int fields = mMetadata == null ? 0 : mMetadata.size(); 500 MediaDescription description = mMetadata == null ? null : mMetadata 501 .getDescription(); 502 return "size=" + fields + ", description=" + description; 503 } 504 505 private void pushPlaybackStateUpdate() { 506 synchronized (mLock) { 507 if (mDestroyed) { 508 return; 509 } 510 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 511 ISessionControllerCallback cb = mControllerCallbacks.get(i); 512 try { 513 cb.onPlaybackStateChanged(mPlaybackState); 514 } catch (DeadObjectException e) { 515 mControllerCallbacks.remove(i); 516 Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e); 517 } catch (RemoteException e) { 518 Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e); 519 } 520 } 521 } 522 } 523 524 private void pushMetadataUpdate() { 525 synchronized (mLock) { 526 if (mDestroyed) { 527 return; 528 } 529 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 530 ISessionControllerCallback cb = mControllerCallbacks.get(i); 531 try { 532 cb.onMetadataChanged(mMetadata); 533 } catch (DeadObjectException e) { 534 Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e); 535 mControllerCallbacks.remove(i); 536 } catch (RemoteException e) { 537 Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e); 538 } 539 } 540 } 541 } 542 543 private void pushQueueUpdate() { 544 synchronized (mLock) { 545 if (mDestroyed) { 546 return; 547 } 548 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 549 ISessionControllerCallback cb = mControllerCallbacks.get(i); 550 try { 551 cb.onQueueChanged(mQueue); 552 } catch (DeadObjectException e) { 553 mControllerCallbacks.remove(i); 554 Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e); 555 } catch (RemoteException e) { 556 Log.w(TAG, "unexpected exception in pushQueueUpdate.", e); 557 } 558 } 559 } 560 } 561 562 private void pushQueueTitleUpdate() { 563 synchronized (mLock) { 564 if (mDestroyed) { 565 return; 566 } 567 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 568 ISessionControllerCallback cb = mControllerCallbacks.get(i); 569 try { 570 cb.onQueueTitleChanged(mQueueTitle); 571 } catch (DeadObjectException e) { 572 mControllerCallbacks.remove(i); 573 Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e); 574 } catch (RemoteException e) { 575 Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e); 576 } 577 } 578 } 579 } 580 581 private void pushExtrasUpdate() { 582 synchronized (mLock) { 583 if (mDestroyed) { 584 return; 585 } 586 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 587 ISessionControllerCallback cb = mControllerCallbacks.get(i); 588 try { 589 cb.onExtrasChanged(mExtras); 590 } catch (DeadObjectException e) { 591 mControllerCallbacks.remove(i); 592 Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e); 593 } catch (RemoteException e) { 594 Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e); 595 } 596 } 597 } 598 } 599 600 private void pushVolumeUpdate() { 601 synchronized (mLock) { 602 if (mDestroyed) { 603 return; 604 } 605 ParcelableVolumeInfo info = mController.getVolumeAttributes(); 606 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 607 ISessionControllerCallback cb = mControllerCallbacks.get(i); 608 try { 609 cb.onVolumeInfoChanged(info); 610 } catch (DeadObjectException e) { 611 Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e); 612 } catch (RemoteException e) { 613 Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e); 614 } 615 } 616 } 617 } 618 619 private void pushEvent(String event, Bundle data) { 620 synchronized (mLock) { 621 if (mDestroyed) { 622 return; 623 } 624 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 625 ISessionControllerCallback cb = mControllerCallbacks.get(i); 626 try { 627 cb.onEvent(event, data); 628 } catch (DeadObjectException e) { 629 Log.w(TAG, "Removing dead callback in pushEvent.", e); 630 mControllerCallbacks.remove(i); 631 } catch (RemoteException e) { 632 Log.w(TAG, "unexpected exception in pushEvent.", e); 633 } 634 } 635 } 636 } 637 638 private void pushSessionDestroyed() { 639 synchronized (mLock) { 640 // This is the only method that may be (and can only be) called 641 // after the session is destroyed. 642 if (!mDestroyed) { 643 return; 644 } 645 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 646 ISessionControllerCallback cb = mControllerCallbacks.get(i); 647 try { 648 cb.onSessionDestroyed(); 649 } catch (DeadObjectException e) { 650 Log.w(TAG, "Removing dead callback in pushEvent.", e); 651 mControllerCallbacks.remove(i); 652 } catch (RemoteException e) { 653 Log.w(TAG, "unexpected exception in pushEvent.", e); 654 } 655 } 656 // After notifying clear all listeners 657 mControllerCallbacks.clear(); 658 } 659 } 660 661 private PlaybackState getStateWithUpdatedPosition() { 662 PlaybackState state; 663 long duration = -1; 664 synchronized (mLock) { 665 state = mPlaybackState; 666 if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 667 duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 668 } 669 } 670 PlaybackState result = null; 671 if (state != null) { 672 if (state.getState() == PlaybackState.STATE_PLAYING 673 || state.getState() == PlaybackState.STATE_FAST_FORWARDING 674 || state.getState() == PlaybackState.STATE_REWINDING) { 675 long updateTime = state.getLastPositionUpdateTime(); 676 long currentTime = SystemClock.elapsedRealtime(); 677 if (updateTime > 0) { 678 long position = (long) (state.getPlaybackSpeed() 679 * (currentTime - updateTime)) + state.getPosition(); 680 if (duration >= 0 && position > duration) { 681 position = duration; 682 } else if (position < 0) { 683 position = 0; 684 } 685 PlaybackState.Builder builder = new PlaybackState.Builder(state); 686 builder.setState(state.getState(), position, state.getPlaybackSpeed(), 687 currentTime); 688 result = builder.build(); 689 } 690 } 691 } 692 return result == null ? state : result; 693 } 694 695 private int getControllerCbIndexForCb(ISessionControllerCallback cb) { 696 IBinder binder = cb.asBinder(); 697 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 698 if (binder.equals(mControllerCallbacks.get(i).asBinder())) { 699 return i; 700 } 701 } 702 return -1; 703 } 704 705 private final Runnable mClearOptimisticVolumeRunnable = new Runnable() { 706 @Override 707 public void run() { 708 boolean needUpdate = (mOptimisticVolume != mCurrentVolume); 709 mOptimisticVolume = -1; 710 if (needUpdate) { 711 pushVolumeUpdate(); 712 } 713 } 714 }; 715 716 private final class SessionStub extends ISession.Stub { 717 @Override 718 public void destroy() { 719 mService.destroySession(MediaSessionRecord.this); 720 } 721 722 @Override 723 public void sendEvent(String event, Bundle data) { 724 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, 725 data == null ? null : new Bundle(data)); 726 } 727 728 @Override 729 public ISessionController getController() { 730 return mController; 731 } 732 733 @Override 734 public void setActive(boolean active) { 735 mIsActive = active; 736 mService.updateSession(MediaSessionRecord.this); 737 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 738 } 739 740 @Override 741 public void setFlags(int flags) { 742 if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 743 int pid = getCallingPid(); 744 int uid = getCallingUid(); 745 mService.enforcePhoneStatePermission(pid, uid); 746 } 747 mFlags = flags; 748 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 749 } 750 751 @Override 752 public void setMediaButtonReceiver(PendingIntent pi) { 753 mMediaButtonReceiver = pi; 754 } 755 756 @Override 757 public void setLaunchPendingIntent(PendingIntent pi) { 758 mLaunchIntent = pi; 759 } 760 761 @Override 762 public void setMetadata(MediaMetadata metadata) { 763 synchronized (mLock) { 764 MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata) 765 .build(); 766 // This is to guarantee that the underlying bundle is unparceled 767 // before we set it to prevent concurrent reads from throwing an 768 // exception 769 if (temp != null) { 770 temp.size(); 771 } 772 mMetadata = temp; 773 } 774 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 775 } 776 777 @Override 778 public void setPlaybackState(PlaybackState state) { 779 int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); 780 int newState = state == null ? 0 : state.getState(); 781 if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { 782 mLastActiveTime = SystemClock.elapsedRealtime(); 783 } 784 synchronized (mLock) { 785 mPlaybackState = state; 786 } 787 mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); 788 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 789 } 790 791 @Override 792 public void setQueue(ParceledListSlice queue) { 793 synchronized (mLock) { 794 mQueue = queue; 795 } 796 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE); 797 } 798 799 @Override 800 public void setQueueTitle(CharSequence title) { 801 mQueueTitle = title; 802 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE); 803 } 804 805 @Override 806 public void setExtras(Bundle extras) { 807 synchronized (mLock) { 808 mExtras = extras == null ? null : new Bundle(extras); 809 } 810 mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS); 811 } 812 813 @Override 814 public void setRatingType(int type) { 815 mRatingType = type; 816 } 817 818 @Override 819 public void setCurrentVolume(int volume) { 820 mCurrentVolume = volume; 821 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); 822 } 823 824 @Override 825 public void setPlaybackToLocal(AudioAttributes attributes) { 826 boolean typeChanged; 827 synchronized (mLock) { 828 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE; 829 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; 830 if (attributes != null) { 831 mAudioAttrs = attributes; 832 } else { 833 Log.e(TAG, "Received null audio attributes, using existing attributes"); 834 } 835 } 836 if (typeChanged) { 837 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); 838 } 839 } 840 841 @Override 842 public void setPlaybackToRemote(int control, int max) { 843 boolean typeChanged; 844 synchronized (mLock) { 845 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL; 846 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE; 847 mVolumeControlType = control; 848 mMaxVolume = max; 849 } 850 if (typeChanged) { 851 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); 852 } 853 } 854 } 855 856 class SessionCb { 857 private final ISessionCallback mCb; 858 859 public SessionCb(ISessionCallback cb) { 860 mCb = cb; 861 } 862 863 public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { 864 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 865 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 866 try { 867 mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); 868 return true; 869 } catch (RemoteException e) { 870 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 871 } 872 return false; 873 } 874 875 public void sendCommand(String command, Bundle args, ResultReceiver cb) { 876 try { 877 mCb.onCommand(command, args, cb); 878 } catch (RemoteException e) { 879 Slog.e(TAG, "Remote failure in sendCommand.", e); 880 } 881 } 882 883 public void sendCustomAction(String action, Bundle args) { 884 try { 885 mCb.onCustomAction(action, args); 886 } catch (RemoteException e) { 887 Slog.e(TAG, "Remote failure in sendCustomAction.", e); 888 } 889 } 890 891 public void play() { 892 try { 893 mCb.onPlay(); 894 } catch (RemoteException e) { 895 Slog.e(TAG, "Remote failure in play.", e); 896 } 897 } 898 899 public void playFromMediaId(String mediaId, Bundle extras) { 900 try { 901 mCb.onPlayFromMediaId(mediaId, extras); 902 } catch (RemoteException e) { 903 Slog.e(TAG, "Remote failure in playUri.", e); 904 } 905 } 906 907 public void playFromSearch(String query, Bundle extras) { 908 try { 909 mCb.onPlayFromSearch(query, extras); 910 } catch (RemoteException e) { 911 Slog.e(TAG, "Remote failure in playFromSearch.", e); 912 } 913 } 914 915 public void skipToTrack(long id) { 916 try { 917 mCb.onSkipToTrack(id); 918 } catch (RemoteException e) { 919 Slog.e(TAG, "Remote failure in skipToTrack", e); 920 } 921 } 922 923 public void pause() { 924 try { 925 mCb.onPause(); 926 } catch (RemoteException e) { 927 Slog.e(TAG, "Remote failure in pause.", e); 928 } 929 } 930 931 public void stop() { 932 try { 933 mCb.onStop(); 934 } catch (RemoteException e) { 935 Slog.e(TAG, "Remote failure in stop.", e); 936 } 937 } 938 939 public void next() { 940 try { 941 mCb.onNext(); 942 } catch (RemoteException e) { 943 Slog.e(TAG, "Remote failure in next.", e); 944 } 945 } 946 947 public void previous() { 948 try { 949 mCb.onPrevious(); 950 } catch (RemoteException e) { 951 Slog.e(TAG, "Remote failure in previous.", e); 952 } 953 } 954 955 public void fastForward() { 956 try { 957 mCb.onFastForward(); 958 } catch (RemoteException e) { 959 Slog.e(TAG, "Remote failure in fastForward.", e); 960 } 961 } 962 963 public void rewind() { 964 try { 965 mCb.onRewind(); 966 } catch (RemoteException e) { 967 Slog.e(TAG, "Remote failure in rewind.", e); 968 } 969 } 970 971 public void seekTo(long pos) { 972 try { 973 mCb.onSeekTo(pos); 974 } catch (RemoteException e) { 975 Slog.e(TAG, "Remote failure in seekTo.", e); 976 } 977 } 978 979 public void rate(Rating rating) { 980 try { 981 mCb.onRate(rating); 982 } catch (RemoteException e) { 983 Slog.e(TAG, "Remote failure in rate.", e); 984 } 985 } 986 987 public void adjustVolume(int direction) { 988 try { 989 mCb.onAdjustVolume(direction); 990 } catch (RemoteException e) { 991 Slog.e(TAG, "Remote failure in adjustVolume.", e); 992 } 993 } 994 995 public void setVolumeTo(int value) { 996 try { 997 mCb.onSetVolumeTo(value); 998 } catch (RemoteException e) { 999 Slog.e(TAG, "Remote failure in setVolumeTo.", e); 1000 } 1001 } 1002 } 1003 1004 class ControllerStub extends ISessionController.Stub { 1005 @Override 1006 public void sendCommand(String command, Bundle args, ResultReceiver cb) 1007 throws RemoteException { 1008 mSessionCb.sendCommand(command, args, cb); 1009 } 1010 1011 @Override 1012 public boolean sendMediaButton(KeyEvent mediaButtonIntent) { 1013 return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); 1014 } 1015 1016 @Override 1017 public void registerCallbackListener(ISessionControllerCallback cb) { 1018 synchronized (mLock) { 1019 // If this session is already destroyed tell the caller and 1020 // don't add them. 1021 if (mDestroyed) { 1022 try { 1023 cb.onSessionDestroyed(); 1024 } catch (Exception e) { 1025 // ignored 1026 } 1027 return; 1028 } 1029 if (getControllerCbIndexForCb(cb) < 0) { 1030 mControllerCallbacks.add(cb); 1031 if (DEBUG) { 1032 Log.d(TAG, "registering controller callback " + cb); 1033 } 1034 } 1035 } 1036 } 1037 1038 @Override 1039 public void unregisterCallbackListener(ISessionControllerCallback cb) 1040 throws RemoteException { 1041 synchronized (mLock) { 1042 int index = getControllerCbIndexForCb(cb); 1043 if (index != -1) { 1044 mControllerCallbacks.remove(index); 1045 } 1046 if (DEBUG) { 1047 Log.d(TAG, "unregistering callback " + cb + ". index=" + index); 1048 } 1049 } 1050 } 1051 1052 @Override 1053 public String getPackageName() { 1054 return mPackageName; 1055 } 1056 1057 @Override 1058 public String getTag() { 1059 return mTag; 1060 } 1061 1062 @Override 1063 public PendingIntent getLaunchPendingIntent() { 1064 return mLaunchIntent; 1065 } 1066 1067 @Override 1068 public long getFlags() { 1069 return mFlags; 1070 } 1071 1072 @Override 1073 public ParcelableVolumeInfo getVolumeAttributes() { 1074 synchronized (mLock) { 1075 int type; 1076 int max; 1077 int current; 1078 if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 1079 type = mVolumeControlType; 1080 max = mMaxVolume; 1081 current = mOptimisticVolume != -1 ? mOptimisticVolume 1082 : mCurrentVolume; 1083 } else { 1084 int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); 1085 type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; 1086 max = mAudioManager.getStreamMaxVolume(stream); 1087 current = mAudioManager.getStreamVolume(stream); 1088 } 1089 return new ParcelableVolumeInfo(mVolumeType, mAudioAttrs, type, max, current); 1090 } 1091 } 1092 1093 @Override 1094 public void adjustVolume(int direction, int flags, String packageName) { 1095 int uid = Binder.getCallingUid(); 1096 final long token = Binder.clearCallingIdentity(); 1097 try { 1098 MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false); 1099 } finally { 1100 Binder.restoreCallingIdentity(token); 1101 } 1102 } 1103 1104 @Override 1105 public void setVolumeTo(int value, int flags, String packageName) { 1106 int uid = Binder.getCallingUid(); 1107 final long token = Binder.clearCallingIdentity(); 1108 try { 1109 MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid); 1110 } finally { 1111 Binder.restoreCallingIdentity(token); 1112 } 1113 } 1114 1115 @Override 1116 public void play() throws RemoteException { 1117 mSessionCb.play(); 1118 } 1119 1120 @Override 1121 public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { 1122 mSessionCb.playFromMediaId(mediaId, extras); 1123 } 1124 1125 @Override 1126 public void playFromSearch(String query, Bundle extras) throws RemoteException { 1127 mSessionCb.playFromSearch(query, extras); 1128 } 1129 1130 @Override 1131 public void skipToQueueItem(long id) { 1132 mSessionCb.skipToTrack(id); 1133 } 1134 1135 1136 @Override 1137 public void pause() throws RemoteException { 1138 mSessionCb.pause(); 1139 } 1140 1141 @Override 1142 public void stop() throws RemoteException { 1143 mSessionCb.stop(); 1144 } 1145 1146 @Override 1147 public void next() throws RemoteException { 1148 mSessionCb.next(); 1149 } 1150 1151 @Override 1152 public void previous() throws RemoteException { 1153 mSessionCb.previous(); 1154 } 1155 1156 @Override 1157 public void fastForward() throws RemoteException { 1158 mSessionCb.fastForward(); 1159 } 1160 1161 @Override 1162 public void rewind() throws RemoteException { 1163 mSessionCb.rewind(); 1164 } 1165 1166 @Override 1167 public void seekTo(long pos) throws RemoteException { 1168 mSessionCb.seekTo(pos); 1169 } 1170 1171 @Override 1172 public void rate(Rating rating) throws RemoteException { 1173 mSessionCb.rate(rating); 1174 } 1175 1176 @Override 1177 public void sendCustomAction(String action, Bundle args) 1178 throws RemoteException { 1179 mSessionCb.sendCustomAction(action, args); 1180 } 1181 1182 1183 @Override 1184 public MediaMetadata getMetadata() { 1185 synchronized (mLock) { 1186 return mMetadata; 1187 } 1188 } 1189 1190 @Override 1191 public PlaybackState getPlaybackState() { 1192 return getStateWithUpdatedPosition(); 1193 } 1194 1195 @Override 1196 public ParceledListSlice getQueue() { 1197 synchronized (mLock) { 1198 return mQueue; 1199 } 1200 } 1201 1202 @Override 1203 public CharSequence getQueueTitle() { 1204 return mQueueTitle; 1205 } 1206 1207 @Override 1208 public Bundle getExtras() { 1209 synchronized (mLock) { 1210 return mExtras; 1211 } 1212 } 1213 1214 @Override 1215 public int getRatingType() { 1216 return mRatingType; 1217 } 1218 1219 @Override 1220 public boolean isTransportControlEnabled() { 1221 return MediaSessionRecord.this.isTransportControlEnabled(); 1222 } 1223 } 1224 1225 private class MessageHandler extends Handler { 1226 private static final int MSG_UPDATE_METADATA = 1; 1227 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 1228 private static final int MSG_UPDATE_QUEUE = 3; 1229 private static final int MSG_UPDATE_QUEUE_TITLE = 4; 1230 private static final int MSG_UPDATE_EXTRAS = 5; 1231 private static final int MSG_SEND_EVENT = 6; 1232 private static final int MSG_UPDATE_SESSION_STATE = 7; 1233 private static final int MSG_UPDATE_VOLUME = 8; 1234 private static final int MSG_DESTROYED = 9; 1235 1236 public MessageHandler(Looper looper) { 1237 super(looper); 1238 } 1239 @Override 1240 public void handleMessage(Message msg) { 1241 switch (msg.what) { 1242 case MSG_UPDATE_METADATA: 1243 pushMetadataUpdate(); 1244 break; 1245 case MSG_UPDATE_PLAYBACK_STATE: 1246 pushPlaybackStateUpdate(); 1247 break; 1248 case MSG_UPDATE_QUEUE: 1249 pushQueueUpdate(); 1250 break; 1251 case MSG_UPDATE_QUEUE_TITLE: 1252 pushQueueTitleUpdate(); 1253 break; 1254 case MSG_UPDATE_EXTRAS: 1255 pushExtrasUpdate(); 1256 break; 1257 case MSG_SEND_EVENT: 1258 pushEvent((String) msg.obj, msg.getData()); 1259 break; 1260 case MSG_UPDATE_SESSION_STATE: 1261 // TODO add session state 1262 break; 1263 case MSG_UPDATE_VOLUME: 1264 pushVolumeUpdate(); 1265 break; 1266 case MSG_DESTROYED: 1267 pushSessionDestroyed(); 1268 } 1269 } 1270 1271 public void post(int what) { 1272 post(what, null); 1273 } 1274 1275 public void post(int what, Object obj) { 1276 obtainMessage(what, obj).sendToTarget(); 1277 } 1278 1279 public void post(int what, Object obj, Bundle data) { 1280 Message msg = obtainMessage(what, obj); 1281 msg.setData(data); 1282 msg.sendToTarget(); 1283 } 1284 } 1285 1286} 1287