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