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