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