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

Commit 8cba0d0b authored by Jim Miller's avatar Jim Miller Committed by Android (Google) Code Review
Browse files

Merge "Fix scrubbing behavior on keyguard music transport" into klp-dev

parents c9ffd746 cc747ad2
Loading
Loading
Loading
Loading
+85 −72
Original line number Diff line number Diff line
@@ -60,12 +60,12 @@ import java.util.TimeZone;
 */
public class KeyguardTransportControlView extends FrameLayout {

    private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
    private static final int RESET_TO_METADATA_DELAY = 5000;
    protected static final boolean DEBUG = false;
    protected static final String TAG = "TransportControlView";

    private static final boolean ANIMATE_TRANSITIONS = true;
    protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000;

    private ViewGroup mMetadataContainer;
    private ViewGroup mInfoContainer;
@@ -89,11 +89,9 @@ public class KeyguardTransportControlView extends FrameLayout {
    private ImageView mBadge;

    private boolean mSeekEnabled;
    private boolean mUserSeeking;
    private java.text.DateFormat mFormat;

    private Date mTimeElapsed;
    private Date mTrackDuration;
    private Date mTempDate = new Date();

    /**
     * The metadata which should be populated into the view once we've been attached
@@ -111,18 +109,25 @@ public class KeyguardTransportControlView extends FrameLayout {

        @Override
        public void onClientPlaybackStateUpdate(int state) {
            setSeekBarsEnabled(false);
            updatePlayPauseState(state);
        }

        @Override
        public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
                long currentPosMs, float speed) {
            setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0);
            updatePlayPauseState(state);
            if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
                    ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
                    ", speed=" + speed + ")");

            removeCallbacks(mUpdateSeekBars);
            // Since the music client may be responding to historical events that cause the
            // playback state to change dramatically, wait until things become quiescent before
            // resuming automatic scrub position update.
            if (mTransientSeek.getVisibility() == View.VISIBLE
                    && playbackPositionShouldMove(mCurrentPlayState)) {
                postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR);
            }
        }

        @Override
@@ -136,15 +141,21 @@ public class KeyguardTransportControlView extends FrameLayout {
        }
    };

    private final Runnable mUpdateSeekBars = new Runnable() {
    private class UpdateSeekBarRunnable implements  Runnable {
        public void run() {
            if (updateSeekBars()) {
            boolean seekAble = updateOnce();
            if (seekAble) {
                removeCallbacks(this);
                postDelayed(this, 1000);
            }
        }
        public boolean updateOnce() {
            return updateSeekBars();
        }
    };

    private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable();

    private final Runnable mResetToMetadata = new Runnable() {
        public void run() {
            resetToMetadata();
@@ -163,6 +174,7 @@ public class KeyguardTransportControlView extends FrameLayout {
            }
            if (keyCode != -1) {
                sendMediaButtonClick(keyCode);
                delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
            }
        }
    };
@@ -177,25 +189,67 @@ public class KeyguardTransportControlView extends FrameLayout {
        }
    };

    // This class is here to throttle scrub position updates to the music client
    class FutureSeekRunnable implements Runnable {
        private int mProgress;
        private boolean mPending;

        public void run() {
            scrubTo(mProgress);
            mPending = false;
        }

        void setProgress(int progress) {
            mProgress = progress;
            if (!mPending) {
                mPending = true;
                postDelayed(this, 30);
            }
        }
    };

    // This is here because RemoteControlClient's method isn't visible :/
    private final static boolean playbackPositionShouldMove(int playstate) {
        switch(playstate) {
            case RemoteControlClient.PLAYSTATE_STOPPED:
            case RemoteControlClient.PLAYSTATE_PAUSED:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
            case RemoteControlClient.PLAYSTATE_ERROR:
            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
                return false;
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
            case RemoteControlClient.PLAYSTATE_REWINDING:
            default:
                return true;
        }
    }

    private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable();

    private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
            new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (fromUser) {
                scrubTo(progress);
                mFutureSeekRunnable.setProgress(progress);
                delayResetToMetadata();
            }
                mTempDate.setTime(progress);
                mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
            } else {
                updateSeekDisplay();
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            mUserSeeking = true;
            delayResetToMetadata();
            removeCallbacks(mUpdateSeekBars); // don't update during user interaction
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            mUserSeeking = false;
        }
    };

@@ -247,17 +301,11 @@ public class KeyguardTransportControlView extends FrameLayout {
        if (enabled == mSeekEnabled) return;

        mSeekEnabled = enabled;
        if (mTransientSeek.getVisibility() == VISIBLE) {
        if (mTransientSeek.getVisibility() == VISIBLE && !enabled) {
            mTransientSeek.setVisibility(INVISIBLE);
            mMetadataContainer.setVisibility(VISIBLE);
            mUserSeeking = false;
            cancelResetToMetadata();
        }
        if (enabled) {
            mUpdateSeekBars.run();
        } else {
            removeCallbacks(mUpdateSeekBars);
        }
    }

    public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
@@ -294,6 +342,8 @@ public class KeyguardTransportControlView extends FrameLayout {
        }
        final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
        setEnableMarquee(screenOn);
        // Allow long-press anywhere else in this view to show the seek bar
        setOnLongClickListener(mTransportShowSeekBarListener);
    }

    @Override
@@ -326,7 +376,6 @@ public class KeyguardTransportControlView extends FrameLayout {
        mAudioManager.unregisterRemoteController(mRemoteController);
        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
        mMetadata.clear();
        mUserSeeking = false;
        removeCallbacks(mUpdateSeekBars);
    }

@@ -484,18 +533,12 @@ public class KeyguardTransportControlView extends FrameLayout {

    void updateSeekDisplay() {
        if (mMetadata != null && mRemoteController != null && mFormat != null) {
            if (mTimeElapsed == null) {
                mTimeElapsed = new Date();
            }
            if (mTrackDuration == null) {
                mTrackDuration = new Date();
            }
            mTimeElapsed.setTime(mRemoteController.getEstimatedMediaPosition());
            mTrackDuration.setTime(mMetadata.duration);
            mTransientSeekTimeElapsed.setText(mFormat.format(mTimeElapsed));
            mTransientSeekTimeTotal.setText(mFormat.format(mTrackDuration));
            mTempDate.setTime(mRemoteController.getEstimatedMediaPosition());
            mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
            mTempDate.setTime(mMetadata.duration);
            mTransientSeekTimeTotal.setText(mFormat.format(mTempDate));

            if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTimeElapsed +
            if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate +
                    " duration=" + mMetadata.duration);
        }
    }
@@ -508,10 +551,16 @@ public class KeyguardTransportControlView extends FrameLayout {
            mTransientSeek.setVisibility(INVISIBLE);
            mMetadataContainer.setVisibility(VISIBLE);
            cancelResetToMetadata();
            removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible
        } else {
            mTransientSeek.setVisibility(VISIBLE);
            mMetadataContainer.setVisibility(INVISIBLE);
            delayResetToMetadata();
            if (playbackPositionShouldMove(mCurrentPlayState)) {
                mUpdateSeekBars.run();
            } else {
                mUpdateSeekBars.updateOnce();
            }
        }
        mTransportControlCallback.userActivity();
        return true;
@@ -573,9 +622,6 @@ public class KeyguardTransportControlView extends FrameLayout {
            case RemoteControlClient.PLAYSTATE_PLAYING:
                imageResId = R.drawable.ic_media_pause;
                imageDescId = R.string.keyguard_transport_pause_description;
                if (mSeekEnabled) {
                    mUpdateSeekBars.run();
                }
                break;

            case RemoteControlClient.PLAYSTATE_BUFFERING:
@@ -590,10 +636,9 @@ public class KeyguardTransportControlView extends FrameLayout {
                break;
        }

        if (state != RemoteControlClient.PLAYSTATE_PLAYING) {
            removeCallbacks(mUpdateSeekBars);
            updateSeekBars();
        }
        boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0;
        setSeekBarsEnabled(clientSupportsSeek);

        mBtnPlay.setImageResource(imageResId);
        mBtnPlay.setContentDescription(getResources().getString(imageDescId));
        mCurrentPlayState = state;
@@ -601,11 +646,9 @@ public class KeyguardTransportControlView extends FrameLayout {

    boolean updateSeekBars() {
        final int position = (int) mRemoteController.getEstimatedMediaPosition();
        if (DEBUG) Log.v(TAG, "Estimated time:" + position);
        if (position >= 0) {
            if (DEBUG) Log.v(TAG, "Seek to " + position);
            if (!mUserSeeking) {
            mTransientSeekBar.setProgress(position);
            }
            return true;
        }
        Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
@@ -671,34 +714,4 @@ public class KeyguardTransportControlView extends FrameLayout {
    public boolean providesClock() {
        return false;
    }

    private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
        switch (state) {
            case RemoteControlClient.PLAYSTATE_PLAYING:
            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
            case RemoteControlClient.PLAYSTATE_REWINDING:
            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
            case RemoteControlClient.PLAYSTATE_BUFFERING:
                // actively playing or about to play
                return true;
            case RemoteControlClient.PLAYSTATE_NONE:
                return false;
            case RemoteControlClient.PLAYSTATE_STOPPED:
            case RemoteControlClient.PLAYSTATE_PAUSED:
            case RemoteControlClient.PLAYSTATE_ERROR:
                // we have stopped playing, check how long ago
                if (DEBUG) {
                    if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
                        Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
                    } else {
                        Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
                    }
                }
                return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
            default:
                Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
                return false;
        }
    }
}