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