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

Commit 46c2378d authored by Marie Janssen's avatar Marie Janssen
Browse files

Avrcp: refactor addressed player notifications

The time when audio players change is a time of great changes in the Media
Framework.  Many things are updated at the same time, and controllers
are updating three or four times before Metadata is stable for sending
to the controller.

Accordingly, when media updates happen, we can wait a little bit to
update media info when it changes to let it settle.

Refactor sending addressed player changes, resetting notifications when
we are supposed to, and check for an update whenever we update media.

Only update media at max every second.

Test: connect to carkit, change apps, notice media appears
Bug: 34471252
Change-Id: I6f0a5e169bece69894d1139a2b6615fac6b08a48
(cherry picked from commit 59b1c77f883bd97816332046a3a98be4431ccedd)
parent 204c26d0
Loading
Loading
Loading
Loading
+115 −133
Original line number Diff line number Diff line
@@ -86,8 +86,11 @@ public final class Avrcp {
    private @NonNull PlaybackState mCurrentPlayState;
    private int mA2dpState;
    private int mPlayStatusChangedNT;
    private int mReportedPlayStatus;
    private int mTrackChangedNT;
    private int mPlayPosChangedNT;
    private int mAddrPlayerChangedNT;
    private int mReportedPlayerID;
    private long mPlaybackIntervalMs;
    private long mLastReportedPosition;
    private long mNextPosMs;
@@ -121,7 +124,7 @@ public final class Avrcp {
    private AvrcpMediaRsp mAvrcpMediaRsp;

    /* UID counter to be shared across different files. */
    static short sUIDCounter;
    static short sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;

    /* BTRC features */
    public static final int BTRC_FEAT_METADATA = 0x01;
@@ -157,14 +160,13 @@ public final class Avrcp {
    private static final int MSG_ADJUST_VOLUME = 15;
    private static final int MSG_SET_ABSOLUTE_VOLUME = 16;
    private static final int MSG_ABS_VOL_TIMEOUT = 17;
    private static final int MSG_FAST_FORWARD = 18;
    private static final int MSG_REWIND = 19;
    private static final int MSG_SET_A2DP_AUDIO_STATE = 20;
    private static final int MSG_ADDRESSED_PLAYER_CHANGED_RSP = 21;
    private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 22;
    private static final int MSG_NOW_PLAYING_CHANGED_RSP = 23;
    private static final int MSG_SET_A2DP_AUDIO_STATE = 18;
    private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 19;
    private static final int MSG_NOW_PLAYING_CHANGED_RSP = 20;
    private static final int MSG_UPDATE_MEDIA = 21;

    private static final int CMD_TIMEOUT_DELAY = 2000;
    private static final int MEDIA_DWELL_TIME = 1000;
    private static final int MAX_ERROR_RETRY_TIMES = 6;
    private static final int AVRCP_MAX_VOL = 127;
    private static final int AVRCP_BASE_VOLUME_STEP = 1;
@@ -236,11 +238,13 @@ public final class Avrcp {
        mMediaAttributes = new MediaAttributes(null);
        mLastQueueId = MediaSession.QueueItem.UNKNOWN_ID;
        mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
        mReportedPlayStatus = PLAYSTATUS_ERROR;
        mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
        mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
        mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
        mPlaybackIntervalMs = 0L;
        mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
        mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
        mPlaybackIntervalMs = 0L;
        mLastReportedPosition = -1;
        mNextPosMs = -1;
        mPrevPosMs = -1;
@@ -256,8 +260,8 @@ public final class Avrcp {
        mLastLocalVolume = -1;
        mAbsVolThreshold = 0;
        mVolumeMapping = new HashMap<Integer, Integer>();
        sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
        mCurrAddrPlayerID = NO_PLAYER_ID;
        mReportedPlayerID = mCurrAddrPlayerID;
        mCurrBrowsePlayerID = 0;
        mContext = context;
        mLastUsedPlayerID = 0;
@@ -368,12 +372,12 @@ public final class Avrcp {
        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            if (DEBUG) Log.v(TAG, "onMetadataChanged");
            updateCurrentMediaState(false);
            scheduleMediaUpdate();
        }
        @Override
        public synchronized void onPlaybackStateChanged(PlaybackState state) {
            if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
            updateCurrentMediaState(false);
            scheduleMediaUpdate();
        }

        @Override
@@ -427,7 +431,7 @@ public final class Avrcp {
            case MSG_NATIVE_REQ_GET_PLAY_STATUS:
            {
                byte[] address = (byte[]) msg.obj;
                int btstate = convertPlayStateToPlayStatus(mCurrentPlayState);
                int btstate = getBluetoothPlayState(mCurrentPlayState);
                int length = (int) mMediaAttributes.getLength();
                int position = (int) getPlayPosition();
                if (DEBUG)
@@ -483,18 +487,6 @@ public final class Avrcp {
                mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
                break;

            case MSG_ADDRESSED_PLAYER_CHANGED_RSP:
                // Later addressed players override earlier ones.
                if (hasMessages(MSG_ADDRESSED_PLAYER_CHANGED_RSP)) {
                    Log.i(TAG, "MSG_ADDRESSED_PLAYER_CHANGED_RSP: skip, more changes in queue");
                    break;
                }
                if (DEBUG)
                    Log.v(TAG, "MSG_ADDRESSED_PLAYER_CHANGED_RSP: newAddrPlayer = " + msg.arg1);
                registerNotificationRspAddrPlayerChangedNative(
                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED, msg.arg1, sUIDCounter);
                break;

            case MSG_PLAY_INTERVAL_TIMEOUT:
                sendPlayPosNotificationRsp(false);
                break;
@@ -706,7 +698,7 @@ public final class Avrcp {
            case MSG_SET_A2DP_AUDIO_STATE:
                if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
                mA2dpState = msg.arg1;
                updateCurrentMediaState(false);
                scheduleMediaUpdate();
                break;

            case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
@@ -792,6 +784,13 @@ public final class Avrcp {
                handlePassthroughCmd(msg.arg1, msg.arg2);
                break;

            case MSG_UPDATE_MEDIA:
                if (DEBUG) Log.v(TAG, "MSG_UPDATE_MEDIA");
                // Throttle to once per MEDIA_DWELL_TIME
                removeMessages(MSG_UPDATE_MEDIA);
                updateCurrentMediaState(false);
                break;

            default:
                Log.e(TAG, "unknown message! msg.what=" + msg.what);
                break;
@@ -799,40 +798,59 @@ public final class Avrcp {
        }
    }

    private void updatePlaybackState(PlaybackState state) {
        if (state == null) {
          state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
                PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
    private PlaybackState updatePlaybackState() {
        PlaybackState newState = new PlaybackState.Builder()
                                         .setState(PlaybackState.STATE_NONE,
                                                 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f)
                                         .build();
        synchronized (this) {
            PlaybackState controllerState = null;
            if (mMediaController != null) {
                controllerState = mMediaController.getPlaybackState();
            }

        byte stateBytes = (byte) convertPlayStateToBytes(state.getState());
            if (controllerState != null) {
                newState = controllerState;
            } else if (mAudioManager != null && mAudioManager.isMusicActive()) {
                // Use A2DP state if we don't have a state from MediaControlller
                PlaybackState.Builder builder = new PlaybackState.Builder();
                if (mA2dpState == BluetoothA2dp.STATE_PLAYING) {
                    builder.setState(PlaybackState.STATE_PLAYING,
                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
                } else {
                    builder.setState(PlaybackState.STATE_PAUSED,
                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
                }
                newState = builder.build();
            }
        }

        /* updating play status in global media player list */
        byte newPlayStatus = getBluetoothPlayState(newState);

        /* update play status in global media player list */
        MediaPlayerInfo player = getAddressedPlayerInfo();
        if (player != null) {
            player.setPlayStatus(stateBytes);
        } else {
            Log.w(TAG, "onPlaybackStateChanged: no addressed player id=" + mCurrAddrPlayerID);
            player.setPlayStatus(newPlayStatus);
        }

        int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
        int newPlayStatus = convertPlayStateToPlayStatus(state);

        if (DEBUG) {
            Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): "+
                    "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+
                    "new=" + state + "(" + newPlayStatus + ")");
            Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): " + mReportedPlayStatus
                            + "➡" + newPlayStatus + "(" + newState + ")");
        }

        mCurrentPlayState = state;
        if (newState != null) mCurrentPlayState = newState;

        sendPlayPosNotificationRsp(false);

        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM &&
                (oldPlayStatus != newPlayStatus)) {
            mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
            registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus);
        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
                && (mReportedPlayStatus != newPlayStatus)) {
            sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
        }
        return mCurrentPlayState;
    }

    private void sendPlaybackStatus(int playStatusChangedNT, byte playbackState) {
        registerNotificationRspPlayStatusNative(playStatusChangedNT, playbackState);
        mPlayStatusChangedNT = playStatusChangedNT;
        mReportedPlayStatus = playbackState;
    }

    private void updateTransportControls(int transportControlFlags) {
@@ -962,23 +980,32 @@ public final class Avrcp {
        }
    }

    private void scheduleMediaUpdate() {
        Message msg = mHandler.obtainMessage(MSG_UPDATE_MEDIA);
        mHandler.sendMessageDelayed(msg, MEDIA_DWELL_TIME);
    }

    private void updateCurrentMediaState(boolean registering) {
        if (!registering && mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
                && mReportedPlayerID != mCurrAddrPlayerID) {
            registerNotificationRspAddrPlayerChangedNative(
                    AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
            mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
            // Changing player sends reject to all these, so disable them.
            mReportedPlayerID = mCurrAddrPlayerID;
            mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
            mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
            mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
            // If the player changed, they need to re-request anything here again
            // so we can skip the rest of the update.
            return;
        }

        MediaAttributes currentAttributes;
        PlaybackState newState = null;
        PlaybackState newState = updatePlaybackState();

        synchronized (this) {
            if (mMediaController == null) {
                // Use A2DP state if we don't have a MediaControlller
                boolean isPlaying = (mA2dpState == BluetoothA2dp.STATE_PLAYING)
                        && mAudioManager.isMusicActive();
                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);
                }
                newState = builder.build();
                currentAttributes = new MediaAttributes(null);
            } else {
                newState = mMediaController.getPlaybackState();
@@ -1000,8 +1027,7 @@ public final class Avrcp {
            mMediaAttributes = currentAttributes;
            mLastQueueId = newQueueId;
        }

        updatePlaybackState(newState);
        sendPlayPosNotificationRsp(false);
    }

    private void getRcFeaturesRequestFromNative(byte[] address, int features) {
@@ -1033,9 +1059,10 @@ public final class Avrcp {
    private void processRegisterNotification(byte[] address, int eventId, int param) {
        switch (eventId) {
            case EVT_PLAY_STATUS_CHANGED:
                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
                        convertPlayStateToPlayStatus(mCurrentPlayState));
                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
                updatePlaybackState();
                sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
                        getBluetoothPlayState(mCurrentPlayState));
                break;

            case EVT_TRACK_CHANGED:
@@ -1063,6 +1090,8 @@ public final class Avrcp {
                registerNotificationRspAddrPlayerChangedNative(
                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
                        mCurrAddrPlayerID, sUIDCounter);
                mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                mReportedPlayerID = mCurrAddrPlayerID;
                break;

            case EVENT_UIDS_CHANGED:
@@ -1127,45 +1156,10 @@ public final class Avrcp {
        return mCurrentPlayState.getPosition();
    }

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

            case PlaybackState.STATE_STOPPED:
            case PlaybackState.STATE_NONE:
                playStatus = PLAYSTATUS_STOPPED;
                break;

            case PlaybackState.STATE_PAUSED:
                playStatus = PLAYSTATUS_PAUSED;
                break;

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

            case PlaybackState.STATE_REWINDING:
            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
                playStatus = PLAYSTATUS_REV_SEEK;
                break;

            case PlaybackState.STATE_ERROR:
                playStatus = PLAYSTATUS_ERROR;
                break;

        }
        return playStatus;
    }

    private boolean isPlayingState(PlaybackState state) {
        return (state.getState() == PlaybackState.STATE_PLAYING) ||
                (state.getState() == PlaybackState.STATE_BUFFERING);
    private boolean isPlayingState(@Nullable PlaybackState state) {
        if (state == null) return false;
        return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING)
                || (state.getState() == PlaybackState.STATE_BUFFERING);
    }

    /**
@@ -1626,8 +1620,7 @@ public final class Avrcp {
                    int newAddrID = entry.getKey();
                    if (DEBUG) Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
                    updateCurrentController(newAddrID, mCurrBrowsePlayerID);
                    mHandler.obtainMessage(MSG_ADDRESSED_PLAYER_CHANGED_RSP, newAddrID, 0)
                            .sendToTarget();
                    scheduleMediaUpdate();
                    return;
                }
            }
@@ -1793,7 +1786,7 @@ public final class Avrcp {
    /** Add (or update) a player to the media player list without a controller */
    private boolean addMediaPlayerPackage(String packageName) {
        MediaPlayerInfo info = new MediaPlayerInfo(null, AvrcpConstants.PLAYER_TYPE_AUDIO,
                AvrcpConstants.PLAYER_SUBTYPE_NONE, getPlayStateBytes(null),
                AvrcpConstants.PLAYER_SUBTYPE_NONE, PLAYSTATUS_STOPPED,
                getFeatureBitMask(packageName), packageName, getAppLabel(packageName));
        return addMediaPlayerInfo(info);
    }
@@ -1803,8 +1796,9 @@ public final class Avrcp {
        String packageName = controller.getPackageName();
        MediaPlayerInfo info = new MediaPlayerInfo(MediaController.wrap(controller),
                AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
                getPlayStateBytes(controller.getPlaybackState()), getFeatureBitMask(packageName),
                controller.getPackageName(), getAppLabel(packageName));
                getBluetoothPlayState(controller.getPlaybackState()),
                getFeatureBitMask(packageName), controller.getPackageName(),
                getAppLabel(packageName));
        return addMediaPlayerInfo(info);
    }

@@ -1878,25 +1872,13 @@ public final class Avrcp {
     * utility function to get the playback state of any media player through
     * media controller APIs.
     */
    private byte getPlayStateBytes(PlaybackState pbState) {
        byte playStateBytes = PLAYSTATUS_STOPPED;

        if (pbState != null) {
            playStateBytes = (byte)convertPlayStateToBytes(pbState.getState());
            Log.v(TAG, "getPlayBackState: playStateBytes = " + playStateBytes);
        } else {
            Log.w(TAG, "playState object null, sending playStateBytes = " + playStateBytes);
        }

        return playStateBytes;
    private byte getBluetoothPlayState(PlaybackState pbState) {
        if (pbState == null) {
            Log.w(TAG, "playState object null, sending STOPPED");
            return PLAYSTATUS_STOPPED;
        }

    /*
     * utility function to map framework's play state values to AVRCP spec
     * defined play status values
     */
    private int convertPlayStateToBytes(int playState) {
        switch (playState) {
        switch (pbState.getState()) {
            case PlaybackState.STATE_PLAYING:
            case PlaybackState.STATE_BUFFERING:
                return PLAYSTATUS_PLAYING;
@@ -2143,7 +2125,7 @@ public final class Avrcp {
                }
            }
        }
        updateCurrentMediaState(false);
        scheduleMediaUpdate();
        return registerRsp;
    }

@@ -2720,12 +2702,12 @@ public final class Avrcp {
    // Do not modify without updating the HAL bt_rc.h files.

    // match up with btrc_play_status_t enum of bt_rc.h
    final static int PLAYSTATUS_STOPPED = 0;
    final static int PLAYSTATUS_PLAYING = 1;
    final static int PLAYSTATUS_PAUSED = 2;
    final static int PLAYSTATUS_FWD_SEEK = 3;
    final static int PLAYSTATUS_REV_SEEK = 4;
    final static int PLAYSTATUS_ERROR = 255;
    final static byte PLAYSTATUS_STOPPED = 0;
    final static byte PLAYSTATUS_PLAYING = 1;
    final static byte PLAYSTATUS_PAUSED = 2;
    final static byte PLAYSTATUS_FWD_SEEK = 3;
    final static byte PLAYSTATUS_REV_SEEK = 4;
    final static byte PLAYSTATUS_ERROR = (byte) 255;

    // match up with btrc_media_attr_t enum of bt_rc.h
    final static int MEDIA_ATTR_TITLE = 1;