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