Home | History | Annotate | Download | only in media
      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 
    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      */
    152     public ISession getSessionBinder() {
    153         return mSession;
    154     }
    155 
    156     /**
    157      * Get the binder for the {@link MediaController}.
    158      *
    159      * @return The controller binder apps talk to.
    160      */
    161     public ISessionController getControllerBinder() {
    162         return mController;
    163     }
    164 
    165     /**
    166      * Get the info for this session.
    167      *
    168      * @return Info that identifies this session.
    169      */
    170     public String getPackageName() {
    171         return mPackageName;
    172     }
    173 
    174     /**
    175      * Get the tag for the session.
    176      *
    177      * @return The session's tag.
    178      */
    179     public String getTag() {
    180         return mTag;
    181     }
    182 
    183     /**
    184      * Get the intent the app set for their media button receiver.
    185      *
    186      * @return The pending intent set by the app or null.
    187      */
    188     public PendingIntent getMediaButtonReceiver() {
    189         return mMediaButtonReceiver;
    190     }
    191 
    192     /**
    193      * Get this session's flags.
    194      *
    195      * @return The flags for this session.
    196      */
    197     public long getFlags() {
    198         return mFlags;
    199     }
    200 
    201     /**
    202      * Check if this session has the specified flag.
    203      *
    204      * @param flag The flag to check.
    205      * @return True if this session has that flag set, false otherwise.
    206      */
    207     public boolean hasFlag(int flag) {
    208         return (mFlags & flag) != 0;
    209     }
    210 
    211     /**
    212      * Get the user id this session was created for.
    213      *
    214      * @return The user id for this session.
    215      */
    216     public int getUserId() {
    217         return mUserId;
    218     }
    219 
    220     /**
    221      * Check if this session has system priorty and should receive media buttons
    222      * before any other sessions.
    223      *
    224      * @return True if this is a system priority session, false otherwise
    225      */
    226     public boolean isSystemPriority() {
    227         return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
    228     }
    229 
    230     /**
    231      * Send a volume adjustment to the session owner. Direction must be one of
    232      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
    233      * {@link AudioManager#ADJUST_SAME}.
    234      *
    235      * @param direction The direction to adjust volume in.
    236      * @param flags Any of the flags from {@link AudioManager}.
    237      * @param packageName The package that made the original volume request.
    238      * @param uid The uid that made the original volume request.
    239      * @param useSuggested True to use adjustSuggestedStreamVolume instead of
    240      *            adjustStreamVolume.
    241      */
    242     public void adjustVolume(int direction, int flags, String packageName, int uid,
    243             boolean useSuggested) {
    244         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
    245         if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
    246             flags &= ~AudioManager.FLAG_PLAY_SOUND;
    247         }
    248         if (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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    396     public int getOptimisticVolume() {
    397         return mOptimisticVolume;
    398     }
    399 
    400     public boolean isTransportControlEnabled() {
    401         return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    402     }
    403 
    404     @Override
    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      */
    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 
    423     public ISessionCallback getCallback() {
    424         return mSessionCb.mCb;
    425     }
    426 
    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 
    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
    456     public String toString() {
    457         return mPackageName + "/" + mTag + " (uid=" + mUserId + ")";
    458     }
    459 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    690     private void updateCallingPackage() {
    691         updateCallingPackage(UID_NOT_SET, null);
    692     }
    693 
    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
    730         public void destroy() {
    731             mService.destroySession(MediaSessionRecord.this);
    732         }
    733 
    734         @Override
    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
    741         public ISessionController getController() {
    742             return mController;
    743         }
    744 
    745         @Override
    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
    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
    764         public void setMediaButtonReceiver(PendingIntent pi) {
    765             mMediaButtonReceiver = pi;
    766         }
    767 
    768         @Override
    769         public void setLaunchPendingIntent(PendingIntent pi) {
    770             mLaunchIntent = pi;
    771         }
    772 
    773         @Override
    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
    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
    804         public void setQueue(ParceledListSlice queue) {
    805             synchronized (mLock) {
    806                 mQueue = queue;
    807             }
    808             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
    809         }
    810 
    811         @Override
    812         public void setQueueTitle(CharSequence title) {
    813             mQueueTitle = title;
    814             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
    815         }
    816 
    817         @Override
    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
    826         public void setRatingType(int type) {
    827             mRatingType = type;
    828         }
    829 
    830         @Override
    831         public void setCurrentVolume(int volume) {
    832             mCurrentVolume = volume;
    833             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
    834         }
    835 
    836         @Override
    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
    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
    870         public String getCallingPackage() {
    871             return mCallingPackage;
    872         }
    873     }
    874 
    875     class SessionCb {
    876         private final ISessionCallback mCb;
    877 
    878         public SessionCb(ISessionCallback cb) {
    879             mCb = cb;
    880         }
    881 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
   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 
   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 
   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 
   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 
   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 
   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 
   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
   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
   1072         public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
   1073             updateCallingPackage();
   1074             return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
   1075         }
   1076 
   1077         @Override
   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
   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
   1114         public String getPackageName() {
   1115             return mPackageName;
   1116         }
   1117 
   1118         @Override
   1119         public String getTag() {
   1120             return mTag;
   1121         }
   1122 
   1123         @Override
   1124         public PendingIntent getLaunchPendingIntent() {
   1125             return mLaunchIntent;
   1126         }
   1127 
   1128         @Override
   1129         public long getFlags() {
   1130             return mFlags;
   1131         }
   1132 
   1133         @Override
   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
   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
   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
   1178         public void prepare() throws RemoteException {
   1179             updateCallingPackage();
   1180             mSessionCb.prepare();
   1181         }
   1182 
   1183         @Override
   1184         public void prepareFromMediaId(String mediaId, Bundle extras)
   1185                 throws RemoteException {
   1186             updateCallingPackage();
   1187             mSessionCb.prepareFromMediaId(mediaId, extras);
   1188         }
   1189 
   1190         @Override
   1191         public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
   1192             updateCallingPackage();
   1193             mSessionCb.prepareFromSearch(query, extras);
   1194         }
   1195 
   1196         @Override
   1197         public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
   1198             updateCallingPackage();
   1199             mSessionCb.prepareFromUri(uri, extras);
   1200         }
   1201 
   1202         @Override
   1203         public void play() throws RemoteException {
   1204             updateCallingPackage();
   1205             mSessionCb.play();
   1206         }
   1207 
   1208         @Override
   1209         public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
   1210             updateCallingPackage();
   1211             mSessionCb.playFromMediaId(mediaId, extras);
   1212         }
   1213 
   1214         @Override
   1215         public void playFromSearch(String query, Bundle extras) throws RemoteException {
   1216             updateCallingPackage();
   1217             mSessionCb.playFromSearch(query, extras);
   1218         }
   1219 
   1220         @Override
   1221         public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
   1222             updateCallingPackage();
   1223             mSessionCb.playFromUri(uri, extras);
   1224         }
   1225 
   1226         @Override
   1227         public void skipToQueueItem(long id) {
   1228             updateCallingPackage();
   1229             mSessionCb.skipToTrack(id);
   1230         }
   1231 
   1232         @Override
   1233         public void pause() throws RemoteException {
   1234             updateCallingPackage();
   1235             mSessionCb.pause();
   1236         }
   1237 
   1238         @Override
   1239         public void stop() throws RemoteException {
   1240             updateCallingPackage();
   1241             mSessionCb.stop();
   1242         }
   1243 
   1244         @Override
   1245         public void next() throws RemoteException {
   1246             updateCallingPackage();
   1247             mSessionCb.next();
   1248         }
   1249 
   1250         @Override
   1251         public void previous() throws RemoteException {
   1252             updateCallingPackage();
   1253             mSessionCb.previous();
   1254         }
   1255 
   1256         @Override
   1257         public void fastForward() throws RemoteException {
   1258             updateCallingPackage();
   1259             mSessionCb.fastForward();
   1260         }
   1261 
   1262         @Override
   1263         public void rewind() throws RemoteException {
   1264             updateCallingPackage();
   1265             mSessionCb.rewind();
   1266         }
   1267 
   1268         @Override
   1269         public void seekTo(long pos) throws RemoteException {
   1270             updateCallingPackage();
   1271             mSessionCb.seekTo(pos);
   1272         }
   1273 
   1274         @Override
   1275         public void rate(Rating rating) throws RemoteException {
   1276             updateCallingPackage();
   1277             mSessionCb.rate(rating);
   1278         }
   1279 
   1280         @Override
   1281         public void sendCustomAction(String action, Bundle args)
   1282                 throws RemoteException {
   1283             updateCallingPackage();
   1284             mSessionCb.sendCustomAction(action, args);
   1285         }
   1286 
   1287 
   1288         @Override
   1289         public MediaMetadata getMetadata() {
   1290             synchronized (mLock) {
   1291                 return mMetadata;
   1292             }
   1293         }
   1294 
   1295         @Override
   1296         public PlaybackState getPlaybackState() {
   1297             return getStateWithUpdatedPosition();
   1298         }
   1299 
   1300         @Override
   1301         public ParceledListSlice getQueue() {
   1302             synchronized (mLock) {
   1303                 return mQueue;
   1304             }
   1305         }
   1306 
   1307         @Override
   1308         public CharSequence getQueueTitle() {
   1309             return mQueueTitle;
   1310         }
   1311 
   1312         @Override
   1313         public Bundle getExtras() {
   1314             synchronized (mLock) {
   1315                 return mExtras;
   1316             }
   1317         }
   1318 
   1319         @Override
   1320         public int getRatingType() {
   1321             return mRatingType;
   1322         }
   1323 
   1324         @Override
   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 
   1341         public MessageHandler(Looper looper) {
   1342             super(looper);
   1343         }
   1344         @Override
   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 
   1376         public void post(int what) {
   1377             post(what, null);
   1378         }
   1379 
   1380         public void post(int what, Object obj) {
   1381             obtainMessage(what, obj).sendToTarget();
   1382         }
   1383 
   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