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