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