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