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