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