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