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