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