FileDocCategorySizeDatePackage
MediaSessionRecord.javaAPI DocAndroid 5.1 API45155Thu Mar 12 22:22:42 GMT 2015com.android.server.media

MediaSessionRecord

public class MediaSessionRecord extends Object implements IBinder.DeathRecipient
This is the system implementation of a Session. Apps will interact with the MediaSession wrapper class instead.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final int
ACTIVE_BUFFER
The length of time a session will still be considered active after pausing in ms.
private static final int
OPTIMISTIC_VOLUME_TIMEOUT
The amount of time we'll send an assumed volume after the last volume command before reverting to the last reported volume.
private final MessageHandler
mHandler
private final int
mOwnerPid
private final int
mOwnerUid
private final int
mUserId
private final String
mPackageName
private final String
mTag
private final ControllerStub
mController
private final SessionStub
mSession
private final SessionCb
mSessionCb
private final MediaSessionService
mService
private final boolean
mUseMasterVolume
private final Object
mLock
private final ArrayList
mControllerCallbacks
private long
mFlags
private android.app.PendingIntent
mMediaButtonReceiver
private android.app.PendingIntent
mLaunchIntent
private android.os.Bundle
mExtras
private android.media.MediaMetadata
mMetadata
private android.media.session.PlaybackState
mPlaybackState
private android.content.pm.ParceledListSlice
mQueue
private CharSequence
mQueueTitle
private int
mRatingType
private long
mLastActiveTime
private android.media.AudioAttributes
mAudioAttrs
private android.media.AudioManager
mAudioManager
private android.media.AudioManagerInternal
mAudioManagerInternal
private int
mVolumeType
private int
mVolumeControlType
private int
mMaxVolume
private int
mCurrentVolume
private int
mOptimisticVolume
private boolean
mIsActive
private boolean
mDestroyed
private final Runnable
mClearOptimisticVolumeRunnable
Constructors Summary
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, android.media.session.ISessionCallback cb, String tag, MediaSessionService service, android.os.Handler handler)


            
                    
        mOwnerPid = ownerPid;
        mOwnerUid = ownerUid;
        mUserId = userId;
        mPackageName = ownerPackageName;
        mTag = tag;
        mController = new ControllerStub();
        mSession = new SessionStub();
        mSessionCb = new SessionCb(cb);
        mService = service;
        mHandler = new MessageHandler(handler.getLooper());
        mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
        mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
        mUseMasterVolume = service.getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_useMasterVolume);
    
Methods Summary
public voidadjustVolume(int direction, int flags, java.lang.String packageName, int uid, boolean useSuggested)
Send a volume adjustment to the session owner. Direction must be one of {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}.

param
direction The direction to adjust volume in.
param
flags Any of the flags from {@link AudioManager}.
param
packageName The package that made the original volume request.
param
uid The uid that made the original volume request.
param
useSuggested True to use adjustSuggestedStreamVolume instead of adjustStreamVolume.

        int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
        if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
            flags &= ~AudioManager.FLAG_PLAY_SOUND;
        }
        boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE;
        if (direction > 1) {
            direction = 1;
        } else if (direction < -1) {
            direction = -1;
        }
        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
            if (mUseMasterVolume) {
                // If this device only uses master volume and playback is local
                // just adjust the master volume and return.
                boolean isMasterMute = mAudioManager.isMasterMute();
                if (isMute) {
                    mAudioManagerInternal.setMasterMuteForUid(!isMasterMute,
                            flags, packageName, mService.mICallback, uid);
                } else {
                    mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName,
                            uid);
                    if (isMasterMute) {
                        mAudioManagerInternal.setMasterMuteForUid(false,
                                flags, packageName, mService.mICallback, uid);
                    }
                }
                return;
            }
            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
            boolean isStreamMute = mAudioManager.isStreamMute(stream);
            if (useSuggested) {
                if (AudioSystem.isStreamActive(stream, 0)) {
                    if (isMute) {
                        mAudioManager.setStreamMute(stream, !isStreamMute);
                    } else {
                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
                                flags, packageName, uid);
                        if (isStreamMute && direction != 0) {
                            mAudioManager.setStreamMute(stream, false);
                        }
                    }
                } else {
                    flags |= previousFlagPlaySound;
                    isStreamMute =
                            mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE);
                    if (isMute) {
                        mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE,
                                !isStreamMute);
                    } else {
                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName,
                                uid);
                        if (isStreamMute && direction != 0) {
                            mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE,
                                    false);
                        }
                    }
                }
            } else {
                if (isMute) {
                    mAudioManager.setStreamMute(stream, !isStreamMute);
                } else {
                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
                            packageName, uid);
                    if (isStreamMute && direction != 0) {
                        mAudioManager.setStreamMute(stream, false);
                    }
                }
            }
        } else {
            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
                // Nothing to do, the volume cannot be changed
                return;
            }
            if (isMute) {
                Log.w(TAG, "Muting remote playback is not supported");
                return;
            }
            mSessionCb.adjustVolume(direction);

            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
            mOptimisticVolume = volumeBefore + direction;
            mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
            if (volumeBefore != mOptimisticVolume) {
                pushVolumeUpdate();
            }
            mService.notifyRemoteVolumeChanged(flags, this);

            if (DEBUG) {
                Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
                        + mMaxVolume);
            }
        }
    
public voidbinderDied()

        mService.sessionDied(this);
    
public voiddump(java.io.PrintWriter pw, java.lang.String prefix)

        pw.println(prefix + mTag + " " + this);

        final String indent = prefix + "  ";
        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
                + ", userId=" + mUserId);
        pw.println(indent + "package=" + mPackageName);
        pw.println(indent + "launchIntent=" + mLaunchIntent);
        pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
        pw.println(indent + "active=" + mIsActive);
        pw.println(indent + "flags=" + mFlags);
        pw.println(indent + "rating type=" + mRatingType);
        pw.println(indent + "controllers: " + mControllerCallbacks.size());
        pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
        pw.println(indent + "audioAttrs=" + mAudioAttrs);
        pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
                + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
        pw.println(indent + "metadata:" + getShortMetadataString());
        pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
                + (mQueue == null ? 0 : mQueue.getList().size()));
    
public android.media.AudioAttributesgetAudioAttributes()
Get the local audio stream being used. Only valid if playback type is local.

return
The audio stream the session is using.

        return mAudioAttrs;
    
public android.media.session.ISessionCallbackgetCallback()

        return mSessionCb.mCb;
    
public android.media.session.ISessionControllergetControllerBinder()
Get the binder for the {@link MediaController}.

return
The controller binder apps talk to.

        return mController;
    
private intgetControllerCbIndexForCb(android.media.session.ISessionControllerCallback cb)

        IBinder binder = cb.asBinder();
        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
                return i;
            }
        }
        return -1;
    
public intgetCurrentVolume()
Get the current volume for this session. Only valid if playback type is remote.

return
The current volume of the remote playback.

        return mCurrentVolume;
    
public longgetFlags()
Get this session's flags.

return
The flags for this session.

        return mFlags;
    
public intgetMaxVolume()
Get the max volume that can be set. Only valid if playback type is remote.

return
The max volume that can be set.

        return mMaxVolume;
    
public android.app.PendingIntentgetMediaButtonReceiver()
Get the intent the app set for their media button receiver.

return
The pending intent set by the app or null.

        return mMediaButtonReceiver;
    
public intgetOptimisticVolume()
Get the volume we'd like it to be set to. This is only valid for a short while after a call to adjust or set volume.

return
The current optimistic volume or -1.

        return mOptimisticVolume;
    
public java.lang.StringgetPackageName()
Get the info for this session.

return
Info that identifies this session.

        return mPackageName;
    
public intgetPlaybackType()
Get the type of playback, either local or remote.

return
The current type of playback.

        return mVolumeType;
    
public android.media.session.ISessiongetSessionBinder()
Get the binder for the {@link MediaSession}.

return
The session binder apps talk to.

        return mSession;
    
private java.lang.StringgetShortMetadataString()

        int fields = mMetadata == null ? 0 : mMetadata.size();
        MediaDescription description = mMetadata == null ? null : mMetadata
                .getDescription();
        return "size=" + fields + ", description=" + description;
    
private android.media.session.PlaybackStategetStateWithUpdatedPosition()

        PlaybackState state;
        long duration = -1;
        synchronized (mLock) {
            state = mPlaybackState;
            if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
            }
        }
        PlaybackState result = null;
        if (state != null) {
            if (state.getState() == PlaybackState.STATE_PLAYING
                    || state.getState() == PlaybackState.STATE_FAST_FORWARDING
                    || state.getState() == PlaybackState.STATE_REWINDING) {
                long updateTime = state.getLastPositionUpdateTime();
                long currentTime = SystemClock.elapsedRealtime();
                if (updateTime > 0) {
                    long position = (long) (state.getPlaybackSpeed()
                            * (currentTime - updateTime)) + state.getPosition();
                    if (duration >= 0 && position > duration) {
                        position = duration;
                    } else if (position < 0) {
                        position = 0;
                    }
                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
                            currentTime);
                    result = builder.build();
                }
            }
        }
        return result == null ? state : result;
    
public java.lang.StringgetTag()
Get the tag for the session.

return
The session's tag.

        return mTag;
    
public intgetUserId()
Get the user id this session was created for.

return
The user id for this session.

        return mUserId;
    
public intgetVolumeControl()
Get the type of volume control. Only valid if playback type is remote.

return
The volume control type being used.

        return mVolumeControlType;
    
public booleanhasFlag(int flag)
Check if this session has the specified flag.

param
flag The flag to check.
return
True if this session has that flag set, false otherwise.

        return (mFlags & flag) != 0;
    
public booleanisActive()
Check if this session has been set to active by the app.

return
True if the session is active, false otherwise.

        return mIsActive && !mDestroyed;
    
public booleanisPlaybackActive(boolean includeRecentlyActive)
Check if the session is currently performing playback. This will also return true if the session was recently paused.

param
includeRecentlyActive True if playback that was recently paused should count, false if it shouldn't.
return
True if the session is performing playback, false otherwise.

        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
        if (MediaSession.isActiveState(state)) {
            return true;
        }
        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
            if (inactiveTime < ACTIVE_BUFFER) {
                return true;
            }
        }
        return false;
    
public booleanisSystemPriority()
Check if this session has system priorty and should receive media buttons before any other sessions.

return
True if this is a system priority session, false otherwise

        return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
    
public booleanisTransportControlEnabled()

        return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    
public voidonDestroy()
Finish cleaning up this session, including disconnecting if connected and removing the death observer from the callback binder.

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            mDestroyed = true;
            mHandler.post(MessageHandler.MSG_DESTROYED);
        }
    
private voidpushEvent(java.lang.String event, android.os.Bundle data)

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onEvent(event, data);
                } catch (DeadObjectException e) {
                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
                    mControllerCallbacks.remove(i);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushEvent.", e);
                }
            }
        }
    
private voidpushExtrasUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onExtrasChanged(mExtras);
                } catch (DeadObjectException e) {
                    mControllerCallbacks.remove(i);
                    Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e);
                }
            }
        }
    
private voidpushMetadataUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onMetadataChanged(mMetadata);
                } catch (DeadObjectException e) {
                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
                    mControllerCallbacks.remove(i);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
                }
            }
        }
    
private voidpushPlaybackStateUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onPlaybackStateChanged(mPlaybackState);
                } catch (DeadObjectException e) {
                    mControllerCallbacks.remove(i);
                    Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
                }
            }
        }
    
private voidpushQueueTitleUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onQueueTitleChanged(mQueueTitle);
                } catch (DeadObjectException e) {
                    mControllerCallbacks.remove(i);
                    Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e);
                }
            }
        }
    
private voidpushQueueUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onQueueChanged(mQueue);
                } catch (DeadObjectException e) {
                    mControllerCallbacks.remove(i);
                    Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushQueueUpdate.", e);
                }
            }
        }
    
private voidpushSessionDestroyed()

        synchronized (mLock) {
            // This is the only method that may be (and can only be) called
            // after the session is destroyed.
            if (!mDestroyed) {
                return;
            }
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onSessionDestroyed();
                } catch (DeadObjectException e) {
                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
                    mControllerCallbacks.remove(i);
                } catch (RemoteException e) {
                    Log.w(TAG, "unexpected exception in pushEvent.", e);
                }
            }
            // After notifying clear all listeners
            mControllerCallbacks.clear();
        }
    
private voidpushVolumeUpdate()

        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            ParcelableVolumeInfo info = mController.getVolumeAttributes();
            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
                ISessionControllerCallback cb = mControllerCallbacks.get(i);
                try {
                    cb.onVolumeInfoChanged(info);
                } catch (DeadObjectException e) {
                    Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
                } catch (RemoteException e) {
                    Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
                }
            }
        }
    
public voidsendMediaButton(android.view.KeyEvent ke, int sequenceId, android.os.ResultReceiver cb)

        mSessionCb.sendMediaButton(ke, sequenceId, cb);
    
public voidsetVolumeTo(int value, int flags, java.lang.String packageName, int uid)

        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
            int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
            mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid);
        } else {
            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
                // Nothing to do. The volume can't be set directly.
                return;
            }
            value = Math.max(0, Math.min(value, mMaxVolume));
            mSessionCb.setVolumeTo(value);

            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
            mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
            if (volumeBefore != mOptimisticVolume) {
                pushVolumeUpdate();
            }
            mService.notifyRemoteVolumeChanged(flags, this);

            if (DEBUG) {
                Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
                        + mMaxVolume);
            }
        }
    
public java.lang.StringtoString()

        return mPackageName + "/" + mTag;