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