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