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