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