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

Commit 6da15439 authored by Marie Janssen's avatar Marie Janssen Committed by Andre Eisenbach
Browse files

AVRCP: Use MediaController

RemoteController has been depreciated since M.
MediaController lets us track a lot more things and update data as the
state of audio changes.

This also should fix some bugs related to metadata display not happening
on startup / track change / app change.

Bug: 27178384
Bug: 27745277
Bug: 26837775
Bug: 27534794
Bug: 27153373
Change-Id: I246d9641a5e921ed4434495f5c2dfac67d568952
parent 7be2d6b1
Loading
Loading
Loading
Loading
+144 −158
Original line number Diff line number Diff line
@@ -28,9 +28,10 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.media.RemoteController;
import android.media.RemoteController.MetadataEditor;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -70,11 +71,13 @@ public final class Avrcp {
    private Context mContext;
    private final AudioManager mAudioManager;
    private AvrcpMessageHandler mHandler;
    private RemoteController mRemoteController;
    private RemoteControllerWeak mRemoteControllerCb;
    private MediaSessionManager mMediaSessionManager;
    private MediaSessionChangeListener mSessionChangeListener;
    private MediaController mMediaController;
    private MediaControllerListener mMediaControllerCb;
    private Metadata mMetadata;
    private int mTransportControlFlags;
    private int mCurrentPlayState;
    private PlaybackState mCurrentPlayState;
    private int mPlayStatusChangedNT;
    private int mTrackChangedNT;
    private long mTrackNumber;
@@ -134,10 +137,6 @@ public final class Avrcp {
    private static final int MESSAGE_REWIND = 11;
    private static final int MESSAGE_CHANGE_PLAY_POS = 12;
    private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13;
    private static final int MSG_UPDATE_STATE = 100;
    private static final int MSG_SET_METADATA = 101;
    private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
    private static final int MSG_SET_GENERATION_ID = 104;

    private static final int BUTTON_TIMEOUT_TIME = 2000;
    private static final int BASE_SKIP_AMOUNT = 2000;
@@ -157,11 +156,11 @@ public final class Avrcp {

    private Avrcp(Context context) {
        mMetadata = new Metadata();
        mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
        mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
        mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
        mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
        mTrackNumber = -1L;
        mCurrentPosMs = RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN;
        mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
        mPlayStartTimeMs = -1L;
        mSongLengthMs = 0L;
        mPlaybackIntervalMs = 0L;
@@ -183,6 +182,7 @@ public final class Avrcp {

        initNative();

        mMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
@@ -197,10 +197,14 @@ public final class Avrcp {
        thread.start();
        Looper looper = thread.getLooper();
        mHandler = new AvrcpMessageHandler(looper);
        mRemoteControllerCb = new RemoteControllerWeak(mHandler);
        mRemoteController = new RemoteController(mContext, mRemoteControllerCb);
        mAudioManager.registerRemoteController(mRemoteController);
        mRemoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);

        mSessionChangeListener = new MediaSessionChangeListener();
        mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null, mHandler);
        List<MediaController> sessions = mMediaSessionManager.getActiveSessions(null);
        mMediaControllerCb = new MediaControllerListener();
        if (sessions.size() > 0) {
            updateCurrentMediaController(sessions.get(0));
        }
    }

    public static Avrcp make(Context context) {
@@ -216,7 +220,7 @@ public final class Avrcp {
        if (looper != null) {
            looper.quit();
        }
        mAudioManager.unregisterRemoteController(mRemoteController);
        mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener);
    }

    public void cleanup() {
@@ -225,61 +229,52 @@ public final class Avrcp {
            mVolumeMapping.clear();
    }

    private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener {
        private final WeakReference<Handler> mLocalHandler;

        public RemoteControllerWeak(Handler handler) {
            mLocalHandler = new WeakReference<Handler>(handler);
    private class MediaControllerListener extends MediaController.Callback {
        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            Log.v(TAG, "MediaController metadata changed");
            updateMetadata(metadata);
        }

        @Override
        public void onClientChange(boolean clearing) {
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_SET_GENERATION_ID,
                        0, (clearing ? 1 : 0), null).sendToTarget();
            }
        public void onPlaybackStateChanged(PlaybackState state) {
            Log.v(TAG, "MediaController playback changed: " + state.toString());
            updatePlayPauseState(state);
        }

        @Override
        public void onClientPlaybackStateUpdate(int state) {
            // Should never be called with the existing code, but just in case
            if (DEBUG) Log.v(TAG, "RemoteControlDisplayer: Update playbackState state=" + state + " position=null");
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
                        new Long(RemoteControlClient.PLAYBACK_POSITION_INVALID)).sendToTarget();
        public void onSessionDestroyed() {
            Log.v(TAG, "MediaController session destroyed");
        }
    }

        @Override
        public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
                long currentPosMs, float speed) {
            if (DEBUG) Log.v(TAG, "RemoteControlDisplayer: Update playbackState state=" + state + " position=" + currentPosMs);
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_UPDATE_STATE, 0, state,
                        new Long(currentPosMs)).sendToTarget();
            }
    private class MediaSessionChangeListener implements MediaSessionManager.OnActiveSessionsChangedListener {
        public MediaSessionChangeListener() {
        }

        @Override
        public void onClientTransportControlUpdate(int transportControlFlags) {
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, 0, transportControlFlags)
                        .sendToTarget();
        public void onActiveSessionsChanged(List<MediaController> controllers) {
            Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions");
            if (controllers.size() > 0) {
                updateCurrentMediaController(controllers.get(0));
            }
        }
    }

        @Override
        public void onClientMetadataUpdate(MetadataEditor metadataEditor) {
            Log.v(TAG, "RemoteControlDisplayer: Update Metadata");
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_SET_METADATA, 0, 0, metadataEditor).sendToTarget();
    private void updateCurrentMediaController(MediaController controller) {
        Log.v(TAG, "Updating media controller to " + controller);
        if (mMediaController != null) {
            mMediaController.unregisterCallback(mMediaControllerCb);
        }
        mMediaController = controller;
        if (mMediaController == null) {
            updateMetadata(null);
            updatePlayPauseState(null);
            return;
        }
        mMediaController.registerCallback(mMediaControllerCb, mHandler);
        updateMetadata(mMediaController.getMetadata());
        updatePlayPauseState(mMediaController.getPlaybackState());
    }

    /** Handles Avrcp messages. */
@@ -291,22 +286,6 @@ public final class Avrcp {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_UPDATE_STATE:
                updatePlayPauseState(msg.arg2, ((Long) msg.obj).longValue());
                break;

            case MSG_SET_METADATA:
                updateMetadata((MetadataEditor) msg.obj);
                break;

            case MSG_SET_TRANSPORT_CONTROLS:
                updateTransportControls(msg.arg2);
                break;

            case MSG_SET_GENERATION_ID:
                Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
                break;

            case MESSAGE_GET_RC_FEATURES:
                String address = (String) msg.obj;
                if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
@@ -395,8 +374,7 @@ public final class Avrcp {
                    }
                }

                if (mLocalVolume != volIndex &&
                                                   (msg.arg2 == AVRC_RSP_ACCEPT ||
                if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT ||
                                                 msg.arg2 == AVRC_RSP_CHANGED ||
                                                 msg.arg2 == AVRC_RSP_INTERIM)) {
                    /* If the volume has successfully changed */
@@ -567,22 +545,22 @@ public final class Avrcp {
            case MESSAGE_FAST_FORWARD:
            case MESSAGE_REWIND:
                if (msg.what == MESSAGE_FAST_FORWARD) {
                    if((mTransportControlFlags &
                        RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD) != 0) {
                    if ((mCurrentPlayState.getActions() &
                                PlaybackState.ACTION_FAST_FORWARD) != 0) {
                        int keyState = msg.arg1 == KEY_STATE_PRESS ?
                                KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
                        KeyEvent keyEvent =
                                new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
                    mRemoteController.sendMediaKeyEvent(keyEvent);
                        mMediaController.dispatchMediaButtonEvent(keyEvent);
                        break;
                    }
                } else if((mTransportControlFlags &
                        RemoteControlClient.FLAG_KEY_MEDIA_REWIND) != 0) {
                } else if ((mCurrentPlayState.getActions() &
                            PlaybackState.ACTION_REWIND) != 0) {
                    int keyState = msg.arg1 == KEY_STATE_PRESS ?
                            KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
                    KeyEvent keyEvent =
                            new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND);
                    mRemoteController.sendMediaKeyEvent(keyEvent);
                    mMediaController.dispatchMediaButtonEvent(keyEvent);
                    break;
                }

@@ -640,40 +618,53 @@ public final class Avrcp {
        boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING);
        if (isPlaying != isPlayingState(mCurrentPlayState)) {
            /* if a2dp is streaming, check to make sure music is active */
            if ( (isPlaying) && !mAudioManager.isMusicActive())
            if (isPlaying && !mAudioManager.isMusicActive())
                return;
            updatePlayPauseState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING :
                                 RemoteControlClient.PLAYSTATE_PAUSED,
                                 RemoteControlClient.PLAYBACK_POSITION_INVALID);
            PlaybackState.Builder builder = new PlaybackState.Builder();
            if (isPlaying) {
                builder.setState(PlaybackState.STATE_PLAYING,
                                 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
            } else {
                builder.setState(PlaybackState.STATE_PAUSED,
                                 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
            }
            updatePlayPauseState(builder.build());
        }
    }

    private void updatePlayPauseState(int state, long currentPosMs) {
    private void updatePlayPauseState(PlaybackState state) {
        if (DEBUG) Log.v(TAG,
                "updatePlayPauseState, old=" + mCurrentPlayState + ", state=" + state);
        boolean oldPosValid = (mCurrentPosMs !=
                               RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
                "updatePlayPauseState: old=" + mCurrentPlayState + ", state=" + state);
        if (state == null) {
          state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
                         PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
        }
        boolean oldPosValid = (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN);
        int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
        int newPlayStatus = convertPlayStateToPlayStatus(state);

        if ((mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) &&
        if ((mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) &&
                (mCurrentPlayState != state) && oldPosValid) {
            mCurrentPosMs = getPlayPosition();
        }

        if (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
            mCurrentPosMs = currentPosMs;
        if (state.getState() == PlaybackState.STATE_NONE ||
                state.getState() == PlaybackState.STATE_ERROR) {
            mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
        } else {
            mCurrentPosMs = state.getPosition();
        }
        if ((state == RemoteControlClient.PLAYSTATE_PLAYING) &&
            ((currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) ||
            (mCurrentPlayState != RemoteControlClient.PLAYSTATE_PLAYING))) {

        if ((state.getState() == PlaybackState.STATE_PLAYING) &&
                (mCurrentPlayState.getState() != PlaybackState.STATE_PLAYING)) {
            mPlayStartTimeMs = SystemClock.elapsedRealtime();
        }

        mCurrentPlayState = state;

        boolean newPosValid = (mCurrentPosMs !=
                               RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
        boolean newPosValid = mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN;
        long playPosition = getPlayPosition();

        mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
        /* need send play position changed notification when play status is changed */
        if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) &&
@@ -683,7 +674,7 @@ public final class Avrcp {
            registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)playPosition);
        }
        if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid &&
            (state == RemoteControlClient.PLAYSTATE_PLAYING)) {
                (state.getState() == PlaybackState.STATE_PLAYING)) {
            Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
            mHandler.sendMessageDelayed(msg, mNextPosMs - playPosition);
        }
@@ -715,11 +706,17 @@ public final class Avrcp {
        }
    }

    private void updateMetadata(MetadataEditor data) {
    private void updateMetadata(MediaMetadata data) {
        String oldMetadata = mMetadata.toString();
        mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, null);
        mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null);
        mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null);
        if (data == null) {
            mMetadata = new Metadata();
            mSongLengthMs = 0L;
        } else {
            mMetadata.artist = data.getString(MediaMetadata.METADATA_KEY_ARTIST);
            mMetadata.trackTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
            mMetadata.albumTitle = data.getString(MediaMetadata.METADATA_KEY_ALBUM);
            mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
        }
        if (!oldMetadata.equals(mMetadata.toString())) {
            Log.v(TAG, "Metadata Changed to " + mMetadata.toString());
            mTrackNumber++;
@@ -728,11 +725,10 @@ public final class Avrcp {
                sendTrackChangedRsp();
            }

            if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
                if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
            if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN &&
                isPlayingState(mCurrentPlayState)) {
                mPlayStartTimeMs = SystemClock.elapsedRealtime();
            }
            }
            /* need send play position changed notification when track is changed */
            if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) {
                mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
@@ -744,8 +740,6 @@ public final class Avrcp {
            Log.v(TAG, "Metadata updated but no change!");
        }

        mSongLengthMs = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
                RemoteControlClient.PLAYBACK_POSITION_INVALID);
    }

    private void getRcFeatures(byte[] address, int features) {
@@ -791,10 +785,10 @@ public final class Avrcp {
                long songPosition = getPlayPosition();
                mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM;
                mPlaybackIntervalMs = (long)param * 1000L;
                if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
                if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
                    mNextPosMs = songPosition + mPlaybackIntervalMs;
                    mPrevPosMs = songPosition - mPlaybackIntervalMs;
                    if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
                    if (isPlayingState(mCurrentPlayState)) {
                        Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
                        mHandler.sendMessageDelayed(msg, mPlaybackIntervalMs);
                    }
@@ -830,7 +824,7 @@ public final class Avrcp {
        long currentPosMs = getPlayPosition();
        if (currentPosMs == -1L) return;
        long newPosMs = Math.max(0L, currentPosMs + amount);
        mRemoteController.seekTo(newPosMs);
        mMediaController.getTransportControls().seekTo(newPosMs);
    }

    private int getSkipMultiplier() {
@@ -846,7 +840,7 @@ public final class Avrcp {
           0xFFFFFFFFFFFFFFFF in the interim response */
        long trackNumberRsp = -1L;

        if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
        if (isPlayingState(mCurrentPlayState)) {
            trackNumberRsp = mTrackNumber;
        }

@@ -859,8 +853,8 @@ public final class Avrcp {

    private long getPlayPosition() {
        long songPosition = -1L;
        if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
            if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
        if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
            if (mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) {
                songPosition = SystemClock.elapsedRealtime() -
                        mPlayStartTimeMs + mCurrentPosMs;
            } else {
@@ -900,34 +894,35 @@ public final class Avrcp {
        return attrStr;
    }

    private int convertPlayStateToPlayStatus(int playState) {
    private int convertPlayStateToPlayStatus(PlaybackState state) {
        int playStatus = PLAYSTATUS_ERROR;
        switch (playState) {
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
        switch (state.getState()) {
            case PlaybackState.STATE_PLAYING:
            case PlaybackState.STATE_BUFFERING:
                playStatus = PLAYSTATUS_PLAYING;
                break;

            case RemoteControlClient.PLAYSTATE_STOPPED:
            case RemoteControlClient.PLAYSTATE_NONE:
            case PlaybackState.STATE_STOPPED:
            case PlaybackState.STATE_NONE:
                playStatus = PLAYSTATUS_STOPPED;
                break;

            case RemoteControlClient.PLAYSTATE_PAUSED:
            case PlaybackState.STATE_PAUSED:
                playStatus = PLAYSTATUS_PAUSED;
                break;

            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
            case PlaybackState.STATE_FAST_FORWARDING:
            case PlaybackState.STATE_SKIPPING_TO_NEXT:
            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
                playStatus = PLAYSTATUS_FWD_SEEK;
                break;

            case RemoteControlClient.PLAYSTATE_REWINDING:
            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
            case PlaybackState.STATE_REWINDING:
            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
                playStatus = PLAYSTATUS_REV_SEEK;
                break;

            case RemoteControlClient.PLAYSTATE_ERROR:
            case PlaybackState.STATE_ERROR:
                playStatus = PLAYSTATUS_ERROR;
                break;

@@ -935,18 +930,9 @@ public final class Avrcp {
        return playStatus;
    }

    private boolean isPlayingState(int playState) {
        boolean isPlaying = false;
        switch (playState) {
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
                isPlaying = true;
                break;
            default:
                isPlaying = false;
                break;
        }
        return isPlaying;
    private boolean isPlayingState(PlaybackState state) {
        return (state.getState() == PlaybackState.STATE_PLAYING) ||
                (state.getState() == PlaybackState.STATE_BUFFERING);
    }

    /**