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