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