Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c92856c4 authored by RoboErik's avatar RoboErik Committed by Android (Google) Code Review
Browse files

Merge "b/15330955 Make RemoteController use the new session APIs" into lmp-preview-dev

parents 52769be3 73e23e22
Loading
Loading
Loading
Loading
+28 −19
Original line number Diff line number Diff line
@@ -2344,6 +2344,10 @@ public class AudioManager {
        if (rctlr == null) {
            return false;
        }
        if (USE_SESSIONS) {
            rctlr.startListeningToSessions();
            return true;
        } else {
            IAudioService service = getService();
            final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
            final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
@@ -2359,6 +2363,7 @@ public class AudioManager {
                return false;
            }
        }
    }

    /**
     * Unregisters a {@link RemoteController}, causing it to no longer receive media metadata and
@@ -2369,6 +2374,9 @@ public class AudioManager {
        if (rctlr == null) {
            return;
        }
        if (USE_SESSIONS) {
            rctlr.stopListeningToSessions();
        } else {
            IAudioService service = getService();
            try {
                service.unregisterRemoteControlDisplay(rctlr.getRcDisplay());
@@ -2377,6 +2385,7 @@ public class AudioManager {
                Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
            }
        }
    }

    /**
     * @hide
+286 −57
Original line number Diff line number Diff line
@@ -19,11 +19,17 @@ package android.media;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.IRemoteControlDisplay;
import android.media.MediaMetadataEditor;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -34,6 +40,7 @@ import android.util.Log;
import android.view.KeyEvent;

import java.lang.ref.WeakReference;
import java.util.List;

/**
 * The RemoteController class is used to control media playback, display and update media metadata
@@ -56,6 +63,7 @@ public final class RemoteController
    private final static int TRANSPORT_UNKNOWN = 0;
    private final static String TAG = "RemoteController";
    private final static boolean DEBUG = false;
    private final static boolean USE_SESSIONS = true;
    private final static Object mGenLock = new Object();
    private final static Object mInfoLock = new Object();
    private final RcDisplay mRcd;
@@ -64,6 +72,11 @@ public final class RemoteController
    private final int mMaxBitmapDimension;
    private MetadataEditor mMetadataEditor;

    private MediaSessionManager mSessionManager;
    private MediaSessionManager.SessionListener mSessionListener
            = new TopTransportSessionListener();
    private MediaController.Callback mSessionCb = new MediaControllerCallback();

    /**
     * Synchronized on mGenLock
     */
@@ -79,6 +92,8 @@ public final class RemoteController
    private int mArtworkWidth = -1;
    private int mArtworkHeight = -1;
    private boolean mEnabled = true;
    // synchronized on mInfoLock, for USE_SESSION apis.
    private MediaController mCurrentSession;

    /**
     * Class constructor.
@@ -123,6 +138,8 @@ public final class RemoteController
        mContext = context;
        mRcd = new RcDisplay(this);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mSessionManager = (MediaSessionManager) context
                .getSystemService(Context.MEDIA_SESSION_SERVICE);

        if (ActivityManager.isLowRamDeviceStatic()) {
            mMaxBitmapDimension = MAX_BITMAP_DIMENSION;
@@ -194,9 +211,16 @@ public final class RemoteController
     * @hide
     */
    public String getRemoteControlClientPackageName() {
        if (USE_SESSIONS) {
            synchronized (mInfoLock) {
                return mCurrentSession != null ? mCurrentSession.getSessionInfo().getPackageName()
                        : null;
            }
        } else {
            return mClientPendingIntentCurrent != null ?
                    mClientPendingIntentCurrent.getCreatorPackage() : null;
        }
    }

    /**
     * Return the estimated playback position of the current media track or a negative value
@@ -215,23 +239,39 @@ public final class RemoteController
     * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
     */
    public long getEstimatedMediaPosition() {
        if (mLastPlaybackInfo != null) {
            if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) {
                return mLastPlaybackInfo.mCurrentPosMs;
        if (USE_SESSIONS) {
            synchronized (mInfoLock) {
                if (mCurrentSession != null) {
                    PlaybackState state = mCurrentSession.getPlaybackState();
                    if (state != null) {
                        return state.getPosition();
                    }
                }
            }
        } else {
            final PlaybackInfo lastPlaybackInfo;
            synchronized (mInfoLock) {
                lastPlaybackInfo = mLastPlaybackInfo;
            }
            if (lastPlaybackInfo != null) {
                if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
                    return lastPlaybackInfo.mCurrentPosMs;
                }

            // Take the current position at the time of state change and estimate.
            final long thenPos = mLastPlaybackInfo.mCurrentPosMs;
                // Take the current position at the time of state change and
                // estimate.
                final long thenPos = lastPlaybackInfo.mCurrentPosMs;
                if (thenPos < 0) {
                    return -1;
                }

                final long now = SystemClock.elapsedRealtime();
            final long then = mLastPlaybackInfo.mStateChangeTimeMs;
                final long then = lastPlaybackInfo.mStateChangeTimeMs;
                final long sinceThen = now - then;
            final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed);
                final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
                return thenPos + scaledSinceThen;
            }
        }
        return -1;
    }

@@ -267,10 +307,19 @@ public final class RemoteController
        if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
            throw new IllegalArgumentException("not a media key event");
        }
        if (USE_SESSIONS) {
            synchronized (mInfoLock) {
                if (mCurrentSession != null) {
                    return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
                }
                return false;
            }
        } else {
            final PendingIntent pi;
            synchronized (mInfoLock) {
                if (!mIsRegistered) {
                Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
                    Log.e(TAG,
                            "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
                    return false;
                }
                if (!mEnabled) {
@@ -292,6 +341,7 @@ public final class RemoteController
                Log.i(TAG, "No-op when sending key click, no receiver right now");
                return false;
            }
        }
        return true;
    }

@@ -311,11 +361,19 @@ public final class RemoteController
        if (timeMs < 0) {
            throw new IllegalArgumentException("illegal negative time value");
        }
        if (USE_SESSIONS) {
            synchronized (mInfoLock) {
                if (mCurrentSession != null) {
                    mCurrentSession.getTransportControls().seekTo(timeMs);
                }
            }
        } else {
            final int genId;
            synchronized (mGenLock) {
                genId = mClientGenerationIdCurrent;
            }
            mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
        }
        return true;
    }

@@ -430,7 +488,6 @@ public final class RemoteController
        return editor;
    }


    /**
     * A class to read the metadata published by a {@link RemoteControlClient}, or send a
     * {@link RemoteControlClient} new values for keys that can be edited.
@@ -477,6 +534,20 @@ public final class RemoteController
            if (!mMetadataChanged) {
                return;
            }
            if (USE_SESSIONS) {
                synchronized (mInfoLock) {
                    if (mCurrentSession != null) {
                        if (mEditorMetadata.containsKey(
                                String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
                            Rating rating = (Rating) getObject(
                                    MediaMetadataEditor.RATING_KEY_BY_USER, null);
                            if (rating != null) {
                                mCurrentSession.getTransportControls().setRating(rating);
                            }
                        }
                    }
                }
            } else {
                final int genId;
                synchronized(mGenLock) {
                    genId = mClientGenerationIdCurrent;
@@ -492,12 +563,13 @@ public final class RemoteController
                    } else {
                        Log.e(TAG, "no metadata to apply");
                    }
                }
            }
            // NOT setting mApplied to true as this type of MetadataEditor will be applied
            // multiple times, whenever the user of a RemoteController needs to change the
            // metadata (e.g. user changes the rating of a song more than once during playback)
            mApplied = false;
        }
        }

    }

@@ -649,6 +721,46 @@ public final class RemoteController
        }
    }

    /**
     * This receives updates when the current session changes. This is
     * registered to receive the updates on the handler thread so it can call
     * directly into the appropriate methods.
     */
    private class MediaControllerCallback extends MediaController.Callback {
        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            onNewPlaybackState(state);
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            onNewMediaMetadata(metadata);
        }
    }

    /**
     * Listens for changes to the active session stack and replaces the
     * currently tracked session if it has changed.
     */
    private class TopTransportSessionListener extends MediaSessionManager.SessionListener {
        @Override
        public void onActiveSessionsChanged(List<MediaController> controllers) {
            int size = controllers.size();
            for (int i = 0; i < size; i++) {
                MediaController controller = controllers.get(i);
                long flags = controller.getFlags();
                // We only care about sessions that handle transport controls,
                // which will be true for apps using RCC
                if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
                    updateController(controller);
                    return;
                }
            }
            updateController(null);
        }

    }

    //==================================================
    // Event handling
    private final EventHandler mEventHandler;
@@ -658,6 +770,8 @@ public final class RemoteController
    private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
    private final static int MSG_CLIENT_CHANGE      = 4;
    private final static int MSG_DISPLAY_ENABLE     = 5;
    private final static int MSG_NEW_PLAYBACK_STATE = 6;
    private final static int MSG_NEW_MEDIA_METADATA = 7;

    private class EventHandler extends Handler {

@@ -686,12 +800,46 @@ public final class RemoteController
                case MSG_DISPLAY_ENABLE:
                    onDisplayEnable(msg.arg1 == 1);
                    break;
                case MSG_NEW_PLAYBACK_STATE:
                    // same as new playback info but using new apis
                    onNewPlaybackState((PlaybackState) msg.obj);
                    break;
                case MSG_NEW_MEDIA_METADATA:
                    onNewMediaMetadata((MediaMetadata) msg.obj);
                    break;
                default:
                    Log.e(TAG, "unknown event " + msg.what);
            }
        }
    }

    /**
     * @hide
     */
    void startListeningToSessions() {
        final ComponentName listenerComponent = new ComponentName(mContext,
                mOnClientUpdateListener.getClass());
        mSessionManager.addActiveSessionsListener(mSessionListener, listenerComponent,
                ActivityManager.getCurrentUser());
        mSessionListener.onActiveSessionsChanged(mSessionManager
                .getActiveSessions(listenerComponent));
        if (DEBUG) {
            Log.d(TAG, "Registered session listener with component " + listenerComponent
                    + " for user " + ActivityManager.getCurrentUser());
        }
    }

    /**
     * @hide
     */
    void stopListeningToSessions() {
        mSessionManager.removeActiveSessionsListener(mSessionListener);
        if (DEBUG) {
            Log.d(TAG, "Unregistered session listener for user "
                    + ActivityManager.getCurrentUser());
        }
    }

    /** If the msg is already queued, replace it with this one. */
    private static final int SENDMSG_REPLACE = 0;
    /** If the msg is already queued, ignore this one and leave the old. */
@@ -713,6 +861,7 @@ public final class RemoteController
        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
    }

    ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
    private void onNewPendingIntent(int genId, PendingIntent pi) {
        synchronized(mGenLock) {
            if (mClientGenerationIdCurrent != genId) {
@@ -848,6 +997,86 @@ public final class RemoteController
        }
    }

    ///////////// These calls are used by the new APIs with Sessions //////////////////////
    private void updateController(MediaController controller) {
        if (DEBUG) {
            Log.d(TAG, "Updating controller to " + controller + " previous controller is "
                    + mCurrentSession);
        }
        synchronized (mInfoLock) {
            if (controller == null) {
                if (mCurrentSession != null) {
                    mCurrentSession.removeCallback(mSessionCb);
                    mCurrentSession = null;
                    sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
                            0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
                }
            } else if (mCurrentSession == null
                    || !controller.getSessionInfo().getId()
                            .equals(mCurrentSession.getSessionInfo().getId())) {
                if (mCurrentSession != null) {
                    mCurrentSession.removeCallback(mSessionCb);
                }
                sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
                        0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
                mCurrentSession = controller;
                mCurrentSession.addCallback(mSessionCb, mEventHandler);

                PlaybackState state = controller.getPlaybackState();
                sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
                        0 /* genId */, 0, state /* obj */, 0 /* delay */);

                MediaMetadata metadata = controller.getMetadata();
                sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
                        0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
            }
            // else same controller, no need to update
        }
    }

    private void onNewPlaybackState(PlaybackState state) {
        final OnClientUpdateListener l;
        synchronized (mInfoLock) {
            l = this.mOnClientUpdateListener;
        }
        if (l != null) {
            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
                    .getRccStateFromState(state.getState());
            if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
                l.onClientPlaybackStateUpdate(playstate);
            } else {
                l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
                        state.getPosition(), state.getPlaybackRate());
            }
            if (state != null) {
                l.onClientTransportControlUpdate(PlaybackState.getRccControlFlagsFromActions(state
                        .getActions()));
            }
        }
    }

    private void onNewMediaMetadata(MediaMetadata metadata) {
        if (metadata == null) {
            // RemoteController only handles non-null metadata
            return;
        }
        final OnClientUpdateListener l;
        final MetadataEditor metadataEditor;
        // prepare the received Bundle to be used inside a MetadataEditor
        synchronized(mInfoLock) {
            l = mOnClientUpdateListener;
            boolean canRate = mCurrentSession != null
                    && mCurrentSession.getRatingType() != Rating.RATING_NONE;
            long editableKeys = canRate ? MediaMetadataEditor.RATING_KEY_BY_USER : 0;
            Bundle legacyMetadata = MediaSessionLegacyHelper.getOldMetadata(metadata);
            mMetadataEditor = new MetadataEditor(legacyMetadata, editableKeys);
            metadataEditor = mMetadataEditor;
        }
        if (l != null) {
            l.onClientMetadataUpdate(metadataEditor);
        }
    }

    //==================================================
    private static class PlaybackInfo {
        int mState;
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ interface ISessionController {
    boolean isTransportControlEnabled();
    void showRoutePicker();
    MediaSessionInfo getSessionInfo();
    long getFlags();

    // These commands are for the TransportController
    void play();
+23 −5
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ public final class MediaController {
    private final Object mLock = new Object();

    private boolean mCbRegistered = false;
    private MediaSessionInfo mInfo;

    private TransportControls mTransportController;

@@ -173,6 +174,21 @@ public final class MediaController {
        }
    }

    /**
     * Get the flags for this session.
     *
     * @return The current set of flags for the session.
     * @hide
     */
    public long getFlags() {
        try {
            return mSessionBinder.getFlags();
        } catch (RemoteException e) {
            Log.wtf(TAG, "Error calling getFlags.", e);
        }
        return 0;
    }

    /**
     * Adds a callback to receive updates from the Session. Updates will be
     * posted on the caller's thread.
@@ -253,12 +269,14 @@ public final class MediaController {
     * @hide
     */
    public MediaSessionInfo getSessionInfo() {
        if (mInfo == null) {
            try {
            return mSessionBinder.getSessionInfo();
                mInfo = mSessionBinder.getSessionInfo();
            } catch (RemoteException e) {
                Log.e(TAG, "Error in getSessionInfo.", e);
            }
        return null;
        }
        return mInfo;
    }

    /*
+86 −0
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
@@ -64,6 +68,88 @@ public class MediaSessionLegacyHelper {
        return sInstance;
    }

    public static Bundle getOldMetadata(MediaMetadata metadata) {
        Bundle oldMetadata = new Bundle();
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM),
                    metadata.getString(MediaMetadata.METADATA_KEY_ALBUM));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_ART)) {
            oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
                    metadata.getBitmap(MediaMetadata.METADATA_KEY_ART));
        } else if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)) {
            // Fall back to album art if the track art wasn't available
            oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
                    metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST),
                    metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST),
                    metadata.getString(MediaMetadata.METADATA_KEY_ARTIST));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_AUTHOR)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR),
                    metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPILATION)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPILATION),
                    metadata.getString(MediaMetadata.METADATA_KEY_COMPILATION));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPOSER)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER),
                    metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_DATE)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE),
                    metadata.getString(MediaMetadata.METADATA_KEY_DATE));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_DISC_NUMBER)) {
            oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER),
                    metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
            oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION),
                    metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_GENRE),
                    metadata.getString(MediaMetadata.METADATA_KEY_GENRE));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
            oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS),
                    metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_RATING)) {
            oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_OTHERS),
                    metadata.getRating(MediaMetadata.METADATA_KEY_RATING));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_USER_RATING)) {
            oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER),
                    metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE),
                    metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
            oldMetadata.putLong(
                    String.valueOf(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER),
                    metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_WRITER)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_WRITER),
                    metadata.getString(MediaMetadata.METADATA_KEY_WRITER));
        }
        if (metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR)) {
            oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
                    metadata.getString(MediaMetadata.METADATA_KEY_YEAR));
        }
        return oldMetadata;
    }

    public MediaSession getSession(PendingIntent pi) {
        SessionHolder holder = mSessions.get(pi);
        return holder == null ? null : holder.mSession;
Loading