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