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