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