MediaSessionRecord.java revision c785a78fb483fe54012175c53d3758b2412de7b9
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 long currentTime = SystemClock.elapsedRealtime(); 509 if (updateTime > 0) { 510 long position = (long) (state.getPlaybackSpeed() 511 * (currentTime - updateTime)) + state.getPosition(); 512 if (duration >= 0 && position > duration) { 513 position = duration; 514 } else if (position < 0) { 515 position = 0; 516 } 517 PlaybackState.Builder builder = new PlaybackState.Builder(state); 518 builder.setState(state.getState(), position, state.getPlaybackSpeed(), 519 currentTime); 520 result = builder.build(); 521 } 522 } 523 } 524 return result == null ? state : result; 525 } 526 527 private int getControllerCbIndexForCb(ISessionControllerCallback cb) { 528 IBinder binder = cb.asBinder(); 529 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 530 if (binder.equals(mControllerCallbacks.get(i).asBinder())) { 531 return i; 532 } 533 } 534 return -1; 535 } 536 537 private final Runnable mClearOptimisticVolumeRunnable = new Runnable() { 538 @Override 539 public void run() { 540 boolean needUpdate = (mOptimisticVolume != mCurrentVolume); 541 mOptimisticVolume = -1; 542 if (needUpdate) { 543 pushVolumeUpdate(); 544 } 545 } 546 }; 547 548 private final class SessionStub extends ISession.Stub { 549 @Override 550 public void destroy() { 551 mService.destroySession(MediaSessionRecord.this); 552 } 553 554 @Override 555 public void sendEvent(String event, Bundle data) { 556 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data); 557 } 558 559 @Override 560 public ISessionController getController() { 561 return mController; 562 } 563 564 @Override 565 public void setActive(boolean active) { 566 mIsActive = active; 567 mService.updateSession(MediaSessionRecord.this); 568 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 569 } 570 571 @Override 572 public void setFlags(int flags) { 573 if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 574 int pid = getCallingPid(); 575 int uid = getCallingUid(); 576 mService.enforcePhoneStatePermission(pid, uid); 577 } 578 mFlags = flags; 579 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 580 } 581 582 @Override 583 public void setMediaRouter(IMediaRouter router) { 584 mMediaRouter = router; 585 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 586 } 587 588 @Override 589 public void setMediaButtonReceiver(ComponentName mbr) { 590 mMediaButtonReceiver = mbr; 591 } 592 593 @Override 594 public void setMetadata(MediaMetadata metadata) { 595 mMetadata = metadata; 596 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 597 } 598 599 @Override 600 public void setPlaybackState(PlaybackState state) { 601 int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); 602 int newState = state == null ? 0 : state.getState(); 603 if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) { 604 mLastActiveTime = SystemClock.elapsedRealtime(); 605 } 606 mPlaybackState = state; 607 mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); 608 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 609 } 610 611 @Override 612 public void setRatingType(int type) { 613 mRatingType = type; 614 } 615 616 @Override 617 public void setCurrentVolume(int volume) { 618 mCurrentVolume = volume; 619 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); 620 } 621 622 @Override 623 public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException { 624 boolean typeChanged = type != mVolumeType; 625 switch(type) { 626 case MediaSession.PLAYBACK_TYPE_LOCAL: 627 mVolumeType = type; 628 int audioStream = arg1; 629 if (isValidStream(audioStream)) { 630 mAudioStream = audioStream; 631 } else { 632 Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream"); 633 mAudioStream = AudioManager.STREAM_MUSIC; 634 } 635 break; 636 case MediaSession.PLAYBACK_TYPE_REMOTE: 637 mVolumeType = type; 638 mVolumeControlType = arg1; 639 mMaxVolume = arg2; 640 break; 641 default: 642 throw new IllegalArgumentException("Volume handling type " + type 643 + " not recognized."); 644 } 645 if (typeChanged) { 646 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); 647 } 648 } 649 650 private boolean isValidStream(int stream) { 651 return stream >= AudioManager.STREAM_VOICE_CALL 652 && stream <= AudioManager.STREAM_NOTIFICATION; 653 } 654 } 655 656 class SessionCb { 657 private final ISessionCallback mCb; 658 659 public SessionCb(ISessionCallback cb) { 660 mCb = cb; 661 } 662 663 public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { 664 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 665 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 666 try { 667 mCb.onMediaButton(mediaButtonIntent, sequenceId, cb); 668 return true; 669 } catch (RemoteException e) { 670 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 671 } 672 return false; 673 } 674 675 public void sendCommand(String command, Bundle extras, ResultReceiver cb) { 676 try { 677 mCb.onCommand(command, extras, cb); 678 } catch (RemoteException e) { 679 Slog.e(TAG, "Remote failure in sendCommand.", e); 680 } 681 } 682 683 public void play() { 684 try { 685 mCb.onPlay(); 686 } catch (RemoteException e) { 687 Slog.e(TAG, "Remote failure in play.", e); 688 } 689 } 690 691 public void pause() { 692 try { 693 mCb.onPause(); 694 } catch (RemoteException e) { 695 Slog.e(TAG, "Remote failure in pause.", e); 696 } 697 } 698 699 public void stop() { 700 try { 701 mCb.onStop(); 702 } catch (RemoteException e) { 703 Slog.e(TAG, "Remote failure in stop.", e); 704 } 705 } 706 707 public void next() { 708 try { 709 mCb.onNext(); 710 } catch (RemoteException e) { 711 Slog.e(TAG, "Remote failure in next.", e); 712 } 713 } 714 715 public void previous() { 716 try { 717 mCb.onPrevious(); 718 } catch (RemoteException e) { 719 Slog.e(TAG, "Remote failure in previous.", e); 720 } 721 } 722 723 public void fastForward() { 724 try { 725 mCb.onFastForward(); 726 } catch (RemoteException e) { 727 Slog.e(TAG, "Remote failure in fastForward.", e); 728 } 729 } 730 731 public void rewind() { 732 try { 733 mCb.onRewind(); 734 } catch (RemoteException e) { 735 Slog.e(TAG, "Remote failure in rewind.", e); 736 } 737 } 738 739 public void seekTo(long pos) { 740 try { 741 mCb.onSeekTo(pos); 742 } catch (RemoteException e) { 743 Slog.e(TAG, "Remote failure in seekTo.", e); 744 } 745 } 746 747 public void rate(Rating rating) { 748 try { 749 mCb.onRate(rating); 750 } catch (RemoteException e) { 751 Slog.e(TAG, "Remote failure in rate.", e); 752 } 753 } 754 755 public void adjustVolumeBy(int delta) { 756 try { 757 mCb.onAdjustVolumeBy(delta); 758 } catch (RemoteException e) { 759 Slog.e(TAG, "Remote failure in adjustVolumeBy.", e); 760 } 761 } 762 763 public void setVolumeTo(int value) { 764 try { 765 mCb.onSetVolumeTo(value); 766 } catch (RemoteException e) { 767 Slog.e(TAG, "Remote failure in adjustVolumeBy.", e); 768 } 769 } 770 } 771 772 class ControllerStub extends ISessionController.Stub { 773 @Override 774 public void sendCommand(String command, Bundle extras, ResultReceiver cb) 775 throws RemoteException { 776 mSessionCb.sendCommand(command, extras, cb); 777 } 778 779 @Override 780 public boolean sendMediaButton(KeyEvent mediaButtonIntent) { 781 return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null); 782 } 783 784 @Override 785 public void registerCallbackListener(ISessionControllerCallback cb) { 786 synchronized (mLock) { 787 if (getControllerCbIndexForCb(cb) < 0) { 788 mControllerCallbacks.add(cb); 789 if (DEBUG) { 790 Log.d(TAG, "registering controller callback " + cb); 791 } 792 } 793 } 794 } 795 796 @Override 797 public void unregisterCallbackListener(ISessionControllerCallback cb) 798 throws RemoteException { 799 synchronized (mLock) { 800 int index = getControllerCbIndexForCb(cb); 801 if (index != -1) { 802 mControllerCallbacks.remove(index); 803 } 804 if (DEBUG) { 805 Log.d(TAG, "unregistering callback " + cb + ". index=" + index); 806 } 807 } 808 } 809 810 @Override 811 public MediaSessionInfo getSessionInfo() { 812 return mSessionInfo; 813 } 814 815 @Override 816 public long getFlags() { 817 return mFlags; 818 } 819 820 @Override 821 public ParcelableVolumeInfo getVolumeAttributes() { 822 synchronized (mLock) { 823 int type; 824 int max; 825 int current; 826 if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) { 827 type = mVolumeControlType; 828 max = mMaxVolume; 829 current = mOptimisticVolume != -1 ? mOptimisticVolume 830 : mCurrentVolume; 831 } else { 832 type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; 833 max = mAudioManager.getStreamMaxVolume(mAudioStream); 834 current = mAudioManager.getStreamVolume(mAudioStream); 835 } 836 return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current); 837 } 838 } 839 840 @Override 841 public void adjustVolumeBy(int delta, int flags) { 842 final long token = Binder.clearCallingIdentity(); 843 try { 844 MediaSessionRecord.this.adjustVolumeBy(delta, flags); 845 } finally { 846 Binder.restoreCallingIdentity(token); 847 } 848 } 849 850 @Override 851 public void setVolumeTo(int value, int flags) { 852 final long token = Binder.clearCallingIdentity(); 853 try { 854 MediaSessionRecord.this.setVolumeTo(value, flags); 855 } finally { 856 Binder.restoreCallingIdentity(token); 857 } 858 } 859 860 @Override 861 public void play() throws RemoteException { 862 mSessionCb.play(); 863 } 864 865 @Override 866 public void pause() throws RemoteException { 867 mSessionCb.pause(); 868 } 869 870 @Override 871 public void stop() throws RemoteException { 872 mSessionCb.stop(); 873 } 874 875 @Override 876 public void next() throws RemoteException { 877 mSessionCb.next(); 878 } 879 880 @Override 881 public void previous() throws RemoteException { 882 mSessionCb.previous(); 883 } 884 885 @Override 886 public void fastForward() throws RemoteException { 887 mSessionCb.fastForward(); 888 } 889 890 @Override 891 public void rewind() throws RemoteException { 892 mSessionCb.rewind(); 893 } 894 895 @Override 896 public void seekTo(long pos) throws RemoteException { 897 mSessionCb.seekTo(pos); 898 } 899 900 @Override 901 public void rate(Rating rating) throws RemoteException { 902 mSessionCb.rate(rating); 903 } 904 905 906 @Override 907 public MediaMetadata getMetadata() { 908 return mMetadata; 909 } 910 911 @Override 912 public PlaybackState getPlaybackState() { 913 return getStateWithUpdatedPosition(); 914 } 915 916 @Override 917 public int getRatingType() { 918 return mRatingType; 919 } 920 921 @Override 922 public boolean isTransportControlEnabled() { 923 return MediaSessionRecord.this.isTransportControlEnabled(); 924 } 925 926 @Override 927 public IMediaRouterDelegate createMediaRouterDelegate( 928 IMediaRouterStateCallback callback) { 929 // todo 930 return null; 931 } 932 } 933 934 private class MessageHandler extends Handler { 935 private static final int MSG_UPDATE_METADATA = 1; 936 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 937 private static final int MSG_SEND_EVENT = 3; 938 private static final int MSG_UPDATE_SESSION_STATE = 4; 939 private static final int MSG_UPDATE_VOLUME = 5; 940 941 public MessageHandler(Looper looper) { 942 super(looper); 943 } 944 @Override 945 public void handleMessage(Message msg) { 946 switch (msg.what) { 947 case MSG_UPDATE_METADATA: 948 pushMetadataUpdate(); 949 break; 950 case MSG_UPDATE_PLAYBACK_STATE: 951 pushPlaybackStateUpdate(); 952 break; 953 case MSG_SEND_EVENT: 954 pushEvent((String) msg.obj, msg.getData()); 955 break; 956 case MSG_UPDATE_SESSION_STATE: 957 // TODO add session state 958 break; 959 case MSG_UPDATE_VOLUME: 960 pushVolumeUpdate(); 961 break; 962 } 963 } 964 965 public void post(int what) { 966 post(what, null); 967 } 968 969 public void post(int what, Object obj) { 970 obtainMessage(what, obj).sendToTarget(); 971 } 972 973 public void post(int what, Object obj, Bundle data) { 974 Message msg = obtainMessage(what, obj); 975 msg.setData(data); 976 msg.sendToTarget(); 977 } 978 } 979 980} 981