MediaSessionRecord.java revision 1ff5b1648a051e9650614f0c0f1b3f449777db81
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.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.media.routing.IMediaRouter; 23import android.media.routing.IMediaRouterDelegate; 24import android.media.routing.IMediaRouterStateCallback; 25import android.media.session.ISessionController; 26import android.media.session.ISessionControllerCallback; 27import android.media.session.ISession; 28import android.media.session.ISessionCallback; 29import android.media.session.MediaController; 30import android.media.session.MediaSession; 31import android.media.session.MediaSessionInfo; 32import android.media.session.PlaybackState; 33import android.media.session.ParcelableVolumeInfo; 34import android.media.AudioManager; 35import android.media.MediaMetadata; 36import android.media.Rating; 37import android.media.VolumeProvider; 38import android.os.Binder; 39import android.os.Bundle; 40import android.os.DeadObjectException; 41import android.os.Handler; 42import android.os.IBinder; 43import android.os.Looper; 44import android.os.Message; 45import android.os.RemoteException; 46import android.os.ResultReceiver; 47import android.os.SystemClock; 48import android.text.TextUtils; 49import android.util.Log; 50import android.util.Pair; 51import android.util.Slog; 52import android.view.KeyEvent; 53 54import java.io.PrintWriter; 55import java.util.ArrayList; 56import java.util.List; 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 MediaSessionInfo mSessionInfo; 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 ComponentName mMediaButtonReceiver; 98 99 // TransportPerformer fields 100 101 private MediaMetadata mMetadata; 102 private PlaybackState mPlaybackState; 103 private int mRatingType; 104 private long mLastActiveTime; 105 // End TransportPerformer fields 106 107 // Volume handling fields 108 private AudioManager mAudioManager; 109 private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL; 110 private int mAudioStream = AudioManager.STREAM_MUSIC; 111 private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; 112 private int mMaxVolume = 0; 113 private int mCurrentVolume = 0; 114 private int mOptimisticVolume = -1; 115 // End volume handling fields 116 117 private boolean mIsActive = false; 118 private boolean mDestroyed = false; 119 120 public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, 121 ISessionCallback cb, String tag, MediaSessionService service, Handler handler) { 122 mOwnerPid = ownerPid; 123 mOwnerUid = ownerUid; 124 mUserId = userId; 125 mSessionInfo = new MediaSessionInfo(UUID.randomUUID().toString(), ownerPackageName, 126 ownerPid); 127 mTag = tag; 128 mController = new ControllerStub(); 129 mSession = new SessionStub(); 130 mSessionCb = new SessionCb(cb); 131 mService = service; 132 mHandler = new MessageHandler(handler.getLooper()); 133 mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE); 134 } 135 136 /** 137 * Get the binder for the {@link MediaSession}. 138 * 139 * @return The session binder apps talk to. 140 */ 141 public ISession getSessionBinder() { 142 return mSession; 143 } 144 145 /** 146 * Get the binder for the {@link MediaController}. 147 * 148 * @return The controller binder apps talk to. 149 */ 150 public ISessionController getControllerBinder() { 151 return mController; 152 } 153 154 /** 155 * Get the info for this session. 156 * 157 * @return Info that identifies this session. 158 */ 159 public MediaSessionInfo getSessionInfo() { 160 return mSessionInfo; 161 } 162 163 public ComponentName getMediaButtonReceiver() { 164 return mMediaButtonReceiver; 165 } 166 167 /** 168 * Get this session's flags. 169 * 170 * @return The flags for this session. 171 */ 172 public long getFlags() { 173 return mFlags; 174 } 175 176 /** 177 * Check if this session has the specified flag. 178 * 179 * @param flag The flag to check. 180 * @return True if this session has that flag set, false otherwise. 181 */ 182 public boolean hasFlag(int flag) { 183 return (mFlags & flag) != 0; 184 } 185 186 /** 187 * Get the user id this session was created for. 188 * 189 * @return The user id for this session. 190 */ 191 public int getUserId() { 192 return mUserId; 193 } 194 195 /** 196 * Check if this session has system priorty and should receive media buttons 197 * before any other sessions. 198 * 199 * @return True if this is a system priority session, false otherwise 200 */ 201 public boolean isSystemPriority() { 202 return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; 203 } 204 205 /** 206 * Send a volume adjustment to the session owner. Direction must be one of 207 * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, 208 * {@link AudioManager#ADJUST_SAME}. 209 * 210 * @param direction The direction to adjust volume in. 211 */ 212 public void adjustVolume(int direction, int flags) { 213 if (isPlaybackActive(false)) { 214 flags &= ~AudioManager.FLAG_PLAY_SOUND; 215 } 216 if (direction > 1) { 217 direction = 1; 218 } else if (direction < -1) { 219 direction = -1; 220 } 221 if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { 222 mAudioManager.adjustStreamVolume(mAudioStream, direction, flags); 223 } else { 224 if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { 225 // Nothing to do, the volume cannot be changed 226 return; 227 } 228 mSessionCb.adjustVolume(direction); 229 230 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); 231 mOptimisticVolume = volumeBefore + direction; 232 mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume)); 233 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); 234 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); 235 if (volumeBefore != mOptimisticVolume) { 236 pushVolumeUpdate(); 237 } 238 239 if (DEBUG) { 240 Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is " 241 + mMaxVolume); 242 } 243 } 244 } 245 246 public void setVolumeTo(int value, int flags) { 247 if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { 248 mAudioManager.setStreamVolume(mAudioStream, value, flags); 249 } else { 250 if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) { 251 // Nothing to do. The volume can't be set directly. 252 return; 253 } 254 value = Math.max(0, Math.min(value, mMaxVolume)); 255 mSessionCb.setVolumeTo(value); 256 257 int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume); 258 mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume)); 259 mHandler.removeCallbacks(mClearOptimisticVolumeRunnable); 260 mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT); 261 if (volumeBefore != mOptimisticVolume) { 262 pushVolumeUpdate(); 263 } 264 265 if (DEBUG) { 266 Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is " 267 + mMaxVolume); 268 } 269 } 270 } 271 272 /** 273 * Check if this session has been set to active by the app. 274 * 275 * @return True if the session is active, false otherwise. 276 */ 277 public boolean isActive() { 278 return mIsActive && !mDestroyed; 279 } 280 281 /** 282 * Check if the session is currently performing playback. This will also 283 * return true if the session was recently paused. 284 * 285 * @param includeRecentlyActive True if playback that was recently paused 286 * should count, false if it shouldn't. 287 * @return True if the session is performing playback, false otherwise. 288 */ 289 public boolean isPlaybackActive(boolean includeRecentlyActive) { 290 int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); 291 if (MediaSession.isActiveState(state)) { 292 return true; 293 } 294 if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) { 295 long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; 296 if (inactiveTime < ACTIVE_BUFFER) { 297 return true; 298 } 299 } 300 return false; 301 } 302 303 /** 304 * Get the type of playback, either local or remote. 305 * 306 * @return The current type of playback. 307 */ 308 public int getPlaybackType() { 309 return mVolumeType; 310 } 311 312 /** 313 * Get the local audio stream being used. Only valid if playback type is 314 * local. 315 * 316 * @return The audio stream the session is using. 317 */ 318 public int getAudioStream() { 319 return mAudioStream; 320 } 321 322 /** 323 * Get the type of volume control. Only valid if playback type is remote. 324 * 325 * @return The volume control type being used. 326 */ 327 public int getVolumeControl() { 328 return mVolumeControlType; 329 } 330 331 /** 332 * Get the max volume that can be set. Only valid if playback type is 333 * remote. 334 * 335 * @return The max volume that can be set. 336 */ 337 public int getMaxVolume() { 338 return mMaxVolume; 339 } 340 341 /** 342 * Get the current volume for this session. Only valid if playback type is 343 * remote. 344 * 345 * @return The current volume of the remote playback. 346 */ 347 public int getCurrentVolume() { 348 return mCurrentVolume; 349 } 350 351 /** 352 * Get the volume we'd like it to be set to. This is only valid for a short 353 * while after a call to adjust or set volume. 354 * 355 * @return The current optimistic volume or -1. 356 */ 357 public int getOptimisticVolume() { 358 return mOptimisticVolume; 359 } 360 361 public boolean isTransportControlEnabled() { 362 return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 363 } 364 365 @Override 366 public void binderDied() { 367 mService.sessionDied(this); 368 } 369 370 /** 371 * Finish cleaning up this session, including disconnecting if connected and 372 * removing the death observer from the callback binder. 373 */ 374 public void onDestroy() { 375 synchronized (mLock) { 376 if (mDestroyed) { 377 return; 378 } 379 mDestroyed = true; 380 } 381 } 382 383 public ISessionCallback getCallback() { 384 return mSessionCb.mCb; 385 } 386 387 public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) { 388 mSessionCb.sendMediaButton(ke, sequenceId, cb); 389 } 390 391 public void dump(PrintWriter pw, String prefix) { 392 pw.println(prefix + mTag + " " + this); 393 394 final String indent = prefix + " "; 395 pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid 396 + ", userId=" + mUserId); 397 pw.println(indent + "info=" + mSessionInfo.toString()); 398 pw.println(indent + "active=" + mIsActive); 399 pw.println(indent + "flags=" + mFlags); 400 pw.println(indent + "rating type=" + mRatingType); 401 pw.println(indent + "controllers: " + mControllerCallbacks.size()); 402 pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString())); 403 pw.println(indent + "metadata:" + getShortMetadataString()); 404 } 405 406 private String getShortMetadataString() { 407 int fields = mMetadata == null ? 0 : mMetadata.size(); 408 String title = mMetadata == null ? null : mMetadata 409 .getString(MediaMetadata.METADATA_KEY_TITLE); 410 return "size=" + fields + ", title=" + title; 411 } 412 413 private void pushPlaybackStateUpdate() { 414 synchronized (mLock) { 415 if (mDestroyed) { 416 return; 417 } 418 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 419 ISessionControllerCallback cb = mControllerCallbacks.get(i); 420 try { 421 cb.onPlaybackStateChanged(mPlaybackState); 422 } catch (DeadObjectException e) { 423 mControllerCallbacks.remove(i); 424 Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e); 425 } catch (RemoteException e) { 426 Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e); 427 } 428 } 429 } 430 } 431 432 private void pushMetadataUpdate() { 433 synchronized (mLock) { 434 if (mDestroyed) { 435 return; 436 } 437 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 438 ISessionControllerCallback cb = mControllerCallbacks.get(i); 439 try { 440 cb.onMetadataChanged(mMetadata); 441 } catch (DeadObjectException e) { 442 Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e); 443 mControllerCallbacks.remove(i); 444 } catch (RemoteException e) { 445 Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e); 446 } 447 } 448 } 449 } 450 451 private void pushVolumeUpdate() { 452 synchronized (mLock) { 453 if (mDestroyed) { 454 return; 455 } 456 ParcelableVolumeInfo info = mController.getVolumeAttributes(); 457 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 458 ISessionControllerCallback cb = mControllerCallbacks.get(i); 459 try { 460 cb.onVolumeInfoChanged(info); 461 } catch (DeadObjectException e) { 462 Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e); 463 } catch (RemoteException e) { 464 Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e); 465 } 466 } 467 } 468 } 469 470 private void pushEvent(String event, Bundle data) { 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.onEvent(event, data); 479 } catch (DeadObjectException e) { 480 Log.w(TAG, "Removing dead callback in pushEvent.", e); 481 mControllerCallbacks.remove(i); 482 } catch (RemoteException e) { 483 Log.w(TAG, "unexpected exception in pushEvent.", e); 484 } 485 } 486 } 487 } 488 489 private PlaybackState getStateWithUpdatedPosition() { 490 PlaybackState state = mPlaybackState; 491 long duration = -1; 492 if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 493 duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 494 } 495 PlaybackState result = null; 496 if (state != null) { 497 if (state.getState() == PlaybackState.STATE_PLAYING 498 || state.getState() == PlaybackState.STATE_FAST_FORWARDING 499 || state.getState() == PlaybackState.STATE_REWINDING) { 500 long updateTime = state.getLastPositionUpdateTime(); 501 long currentTime = SystemClock.elapsedRealtime(); 502 if (updateTime > 0) { 503 long position = (long) (state.getPlaybackSpeed() 504 * (currentTime - updateTime)) + state.getPosition(); 505 if (duration >= 0 && position > duration) { 506 position = duration; 507 } else if (position < 0) { 508 position = 0; 509 } 510 PlaybackState.Builder builder = new PlaybackState.Builder(state); 511 builder.setState(state.getState(), position, state.getPlaybackSpeed(), 512 currentTime); 513 result = builder.build(); 514 } 515 } 516 } 517 return result == null ? state : result; 518 } 519 520 private int getControllerCbIndexForCb(ISessionControllerCallback cb) { 521 IBinder binder = cb.asBinder(); 522 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 523 if (binder.equals(mControllerCallbacks.get(i).asBinder())) { 524 return i; 525 } 526 } 527 return -1; 528 } 529 530 private final Runnable mClearOptimisticVolumeRunnable = new Runnable() { 531 @Override 532 public void run() { 533 boolean needUpdate = (mOptimisticVolume != mCurrentVolume); 534 mOptimisticVolume = -1; 535 if (needUpdate) { 536 pushVolumeUpdate(); 537 } 538 } 539 }; 540 541 private final class SessionStub extends ISession.Stub { 542 @Override 543 public void destroy() { 544 mService.destroySession(MediaSessionRecord.this); 545 } 546 547 @Override 548 public void sendEvent(String event, Bundle data) { 549 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data); 550 } 551 552 @Override 553 public ISessionController getController() { 554 return mController; 555 } 556 557 @Override 558 public void setActive(boolean active) { 559 mIsActive = active; 560 mService.updateSession(MediaSessionRecord.this); 561 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 562 } 563 564 @Override 565 public void setFlags(int flags) { 566 if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 567 int pid = getCallingPid(); 568 int uid = getCallingUid(); 569 mService.enforcePhoneStatePermission(pid, uid); 570 } 571 mFlags = flags; 572 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 573 } 574 575 @Override 576 public void setMediaRouter(IMediaRouter router) { 577 mMediaRouter = router; 578 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 579 } 580 581 @Override 582 public void setMediaButtonReceiver(ComponentName mbr) { 583 mMediaButtonReceiver = mbr; 584 } 585 586 @Override 587 public void setMetadata(MediaMetadata metadata) { 588 mMetadata = metadata; 589 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 590 } 591 592 @Override 593 public void setPlaybackState(PlaybackState state) { 594 int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); 595 int newState = state == null ? 0 : state.getState(); 596 if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { 597 mLastActiveTime = SystemClock.elapsedRealtime(); 598 } 599 mPlaybackState = state; 600 mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); 601 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 602 } 603 604 @Override 605 public void setRatingType(int type) { 606 mRatingType = type; 607 } 608 609 @Override 610 public void setCurrentVolume(int volume) { 611 mCurrentVolume = volume; 612 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); 613 } 614 615 @Override 616 public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException { 617 boolean typeChanged = type != mVolumeType; 618 switch(type) { 619 case MediaSession.PLAYBACK_TYPE_LOCAL: 620 mVolumeType = type; 621 int audioStream = arg1; 622 if (isValidStream(audioStream)) { 623 mAudioStream = audioStream; 624 } else { 625 Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream"); 626 mAudioStream = AudioManager.STREAM_MUSIC; 627 } 628 break; 629 case MediaSession.PLAYBACK_TYPE_REMOTE: 630 mVolumeType = type; 631 mVolumeControlType = arg1; 632 mMaxVolume = arg2; 633 break; 634 default: 635 throw new IllegalArgumentException("Volume handling type " + type 636 + " not recognized."); 637 } 638 if (typeChanged) { 639 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); 640 } 641 } 642 643 private boolean isValidStream(int stream) { 644 return stream >= AudioManager.STREAM_VOICE_CALL 645 && stream <= AudioManager.STREAM_NOTIFICATION; 646 } 647 } 648 649 class SessionCb { 650 private final ISessionCallback mCb; 651 652 public SessionCb(ISessionCallback cb) { 653 mCb = cb; 654 } 655 656 public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { 657 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 658 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 659 try { 660 mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); 661 return true; 662 } catch (RemoteException e) { 663 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 664 } 665 return false; 666 } 667 668 public void sendCommand(String command, Bundle extras, ResultReceiver cb) { 669 try { 670 mCb.onCommand(command, extras, cb); 671 } catch (RemoteException e) { 672 Slog.e(TAG, "Remote failure in sendCommand.", e); 673 } 674 } 675 676 public void play() { 677 try { 678 mCb.onPlay(); 679 } catch (RemoteException e) { 680 Slog.e(TAG, "Remote failure in play.", e); 681 } 682 } 683 684 public void pause() { 685 try { 686 mCb.onPause(); 687 } catch (RemoteException e) { 688 Slog.e(TAG, "Remote failure in pause.", e); 689 } 690 } 691 692 public void stop() { 693 try { 694 mCb.onStop(); 695 } catch (RemoteException e) { 696 Slog.e(TAG, "Remote failure in stop.", e); 697 } 698 } 699 700 public void next() { 701 try { 702 mCb.onNext(); 703 } catch (RemoteException e) { 704 Slog.e(TAG, "Remote failure in next.", e); 705 } 706 } 707 708 public void previous() { 709 try { 710 mCb.onPrevious(); 711 } catch (RemoteException e) { 712 Slog.e(TAG, "Remote failure in previous.", e); 713 } 714 } 715 716 public void fastForward() { 717 try { 718 mCb.onFastForward(); 719 } catch (RemoteException e) { 720 Slog.e(TAG, "Remote failure in fastForward.", e); 721 } 722 } 723 724 public void rewind() { 725 try { 726 mCb.onRewind(); 727 } catch (RemoteException e) { 728 Slog.e(TAG, "Remote failure in rewind.", e); 729 } 730 } 731 732 public void seekTo(long pos) { 733 try { 734 mCb.onSeekTo(pos); 735 } catch (RemoteException e) { 736 Slog.e(TAG, "Remote failure in seekTo.", e); 737 } 738 } 739 740 public void rate(Rating rating) { 741 try { 742 mCb.onRate(rating); 743 } catch (RemoteException e) { 744 Slog.e(TAG, "Remote failure in rate.", e); 745 } 746 } 747 748 public void adjustVolume(int direction) { 749 try { 750 mCb.onAdjustVolume(direction); 751 } catch (RemoteException e) { 752 Slog.e(TAG, "Remote failure in adjustVolume.", e); 753 } 754 } 755 756 public void setVolumeTo(int value) { 757 try { 758 mCb.onSetVolumeTo(value); 759 } catch (RemoteException e) { 760 Slog.e(TAG, "Remote failure in setVolumeTo.", e); 761 } 762 } 763 } 764 765 class ControllerStub extends ISessionController.Stub { 766 @Override 767 public void sendCommand(String command, Bundle extras, ResultReceiver cb) 768 throws RemoteException { 769 mSessionCb.sendCommand(command, extras, cb); 770 } 771 772 @Override 773 public boolean sendMediaButton(KeyEvent mediaButtonIntent) { 774 return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); 775 } 776 777 @Override 778 public void registerCallbackListener(ISessionControllerCallback cb) { 779 synchronized (mLock) { 780 if (getControllerCbIndexForCb(cb) < 0) { 781 mControllerCallbacks.add(cb); 782 if (DEBUG) { 783 Log.d(TAG, "registering controller callback " + cb); 784 } 785 } 786 } 787 } 788 789 @Override 790 public void unregisterCallbackListener(ISessionControllerCallback cb) 791 throws RemoteException { 792 synchronized (mLock) { 793 int index = getControllerCbIndexForCb(cb); 794 if (index != -1) { 795 mControllerCallbacks.remove(index); 796 } 797 if (DEBUG) { 798 Log.d(TAG, "unregistering callback " + cb + ". index=" + index); 799 } 800 } 801 } 802 803 @Override 804 public MediaSessionInfo getSessionInfo() { 805 return mSessionInfo; 806 } 807 808 @Override 809 public long getFlags() { 810 return mFlags; 811 } 812 813 @Override 814 public ParcelableVolumeInfo getVolumeAttributes() { 815 synchronized (mLock) { 816 int type; 817 int max; 818 int current; 819 if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) { 820 type = mVolumeControlType; 821 max = mMaxVolume; 822 current = mOptimisticVolume != -1 ? mOptimisticVolume 823 : mCurrentVolume; 824 } else { 825 type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; 826 max = mAudioManager.getStreamMaxVolume(mAudioStream); 827 current = mAudioManager.getStreamVolume(mAudioStream); 828 } 829 return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current); 830 } 831 } 832 833 @Override 834 public void adjustVolume(int direction, int flags) { 835 final long token = Binder.clearCallingIdentity(); 836 try { 837 MediaSessionRecord.this.adjustVolume(direction, flags); 838 } finally { 839 Binder.restoreCallingIdentity(token); 840 } 841 } 842 843 @Override 844 public void setVolumeTo(int value, int flags) { 845 final long token = Binder.clearCallingIdentity(); 846 try { 847 MediaSessionRecord.this.setVolumeTo(value, flags); 848 } finally { 849 Binder.restoreCallingIdentity(token); 850 } 851 } 852 853 @Override 854 public void play() throws RemoteException { 855 mSessionCb.play(); 856 } 857 858 @Override 859 public void pause() throws RemoteException { 860 mSessionCb.pause(); 861 } 862 863 @Override 864 public void stop() throws RemoteException { 865 mSessionCb.stop(); 866 } 867 868 @Override 869 public void next() throws RemoteException { 870 mSessionCb.next(); 871 } 872 873 @Override 874 public void previous() throws RemoteException { 875 mSessionCb.previous(); 876 } 877 878 @Override 879 public void fastForward() throws RemoteException { 880 mSessionCb.fastForward(); 881 } 882 883 @Override 884 public void rewind() throws RemoteException { 885 mSessionCb.rewind(); 886 } 887 888 @Override 889 public void seekTo(long pos) throws RemoteException { 890 mSessionCb.seekTo(pos); 891 } 892 893 @Override 894 public void rate(Rating rating) throws RemoteException { 895 mSessionCb.rate(rating); 896 } 897 898 899 @Override 900 public MediaMetadata getMetadata() { 901 return mMetadata; 902 } 903 904 @Override 905 public PlaybackState getPlaybackState() { 906 return getStateWithUpdatedPosition(); 907 } 908 909 @Override 910 public int getRatingType() { 911 return mRatingType; 912 } 913 914 @Override 915 public boolean isTransportControlEnabled() { 916 return MediaSessionRecord.this.isTransportControlEnabled(); 917 } 918 919 @Override 920 public IMediaRouterDelegate createMediaRouterDelegate( 921 IMediaRouterStateCallback callback) { 922 // todo 923 return null; 924 } 925 } 926 927 private class MessageHandler extends Handler { 928 private static final int MSG_UPDATE_METADATA = 1; 929 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 930 private static final int MSG_SEND_EVENT = 3; 931 private static final int MSG_UPDATE_SESSION_STATE = 4; 932 private static final int MSG_UPDATE_VOLUME = 5; 933 934 public MessageHandler(Looper looper) { 935 super(looper); 936 } 937 @Override 938 public void handleMessage(Message msg) { 939 switch (msg.what) { 940 case MSG_UPDATE_METADATA: 941 pushMetadataUpdate(); 942 break; 943 case MSG_UPDATE_PLAYBACK_STATE: 944 pushPlaybackStateUpdate(); 945 break; 946 case MSG_SEND_EVENT: 947 pushEvent((String) msg.obj, msg.getData()); 948 break; 949 case MSG_UPDATE_SESSION_STATE: 950 // TODO add session state 951 break; 952 case MSG_UPDATE_VOLUME: 953 pushVolumeUpdate(); 954 break; 955 } 956 } 957 958 public void post(int what) { 959 post(what, null); 960 } 961 962 public void post(int what, Object obj) { 963 obtainMessage(what, obj).sendToTarget(); 964 } 965 966 public void post(int what, Object obj, Bundle data) { 967 Message msg = obtainMessage(what, obj); 968 msg.setData(data); 969 msg.sendToTarget(); 970 } 971 } 972 973} 974