PlaybackControlsPresenter.java

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package androidx.leanback.widget;

import android.content.Context;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.ColorInt;
import androidx.leanback.R;
import androidx.leanback.util.MathUtil;

/**
 * A presenter for a control bar that supports "more actions",
 * and toggling the set of controls between primary and secondary
 * sets of {@link Actions}.
 */
class PlaybackControlsPresenter extends ControlBarPresenter {

    /**
     * The data type expected by this presenter.
     */
    static class BoundData extends ControlBarPresenter.BoundData {
        /**
         * The adapter containing secondary actions.
         */
        ObjectAdapter secondaryActionsAdapter;
    }

    class ViewHolder extends ControlBarPresenter.ViewHolder {
        ObjectAdapter mMoreActionsAdapter;
        ObjectAdapter.DataObserver mMoreActionsObserver;
        final FrameLayout mMoreActionsDock;
        Presenter.ViewHolder mMoreActionsViewHolder;
        boolean mMoreActionsShowing;
        final TextView mCurrentTime;
        final TextView mTotalTime;
        final ProgressBar mProgressBar;
        long mCurrentTimeInMs = -1;         // Hold current time in milliseconds
        long mTotalTimeInMs = -1;           // Hold total time in milliseconds
        long mSecondaryProgressInMs = -1;   // Hold secondary progress in milliseconds
        StringBuilder mTotalTimeStringBuilder = new StringBuilder();
        StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
        int mCurrentTimeMarginStart;
        int mTotalTimeMarginEnd;

        ViewHolder(View rootView) {
            super(rootView);
            mMoreActionsDock = (FrameLayout) rootView.findViewById(R.id.more_actions_dock);
            mCurrentTime = (TextView) rootView.findViewById(R.id.current_time);
            mTotalTime = (TextView) rootView.findViewById(R.id.total_time);
            mProgressBar = (ProgressBar) rootView.findViewById(R.id.playback_progress);
            mMoreActionsObserver = new ObjectAdapter.DataObserver() {
                @Override
                public void onChanged() {
                    if (mMoreActionsShowing) {
                        showControls(mPresenter);
                    }
                }
                @Override
                public void onItemRangeChanged(int positionStart, int itemCount) {
                    if (mMoreActionsShowing) {
                        for (int i = 0; i < itemCount; i++) {
                            bindControlToAction(positionStart + i, mPresenter);
                        }
                    }
                }
            };
            mCurrentTimeMarginStart =
                    ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
            mTotalTimeMarginEnd =
                    ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
        }

        void showMoreActions(boolean show) {
            if (show) {
                if (mMoreActionsViewHolder == null) {
                    Action action = new PlaybackControlsRow.MoreActions(mMoreActionsDock.getContext());
                    mMoreActionsViewHolder = mPresenter.onCreateViewHolder(mMoreActionsDock);
                    mPresenter.onBindViewHolder(mMoreActionsViewHolder, action);
                    mPresenter.setOnClickListener(mMoreActionsViewHolder, new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            toggleMoreActions();
                        }
                    });
                }
                if (mMoreActionsViewHolder.view.getParent() == null) {
                    mMoreActionsDock.addView(mMoreActionsViewHolder.view);
                }
            } else if (mMoreActionsViewHolder != null
                    && mMoreActionsViewHolder.view.getParent() != null) {
                mMoreActionsDock.removeView(mMoreActionsViewHolder.view);
            }
        }

        void toggleMoreActions() {
            mMoreActionsShowing = !mMoreActionsShowing;
            showControls(mPresenter);
        }

        @Override
        ObjectAdapter getDisplayedAdapter() {
            return mMoreActionsShowing ? mMoreActionsAdapter : mAdapter;
        }

        @Override
        int getChildMarginFromCenter(Context context, int numControls) {
            int margin = getControlIconWidth(context);
            if (numControls < 4) {
                margin += getChildMarginBiggest(context);
            } else if (numControls < 6) {
                margin += getChildMarginBigger(context);
            } else {
                margin += getChildMarginDefault(context);
            }
            return margin;
        }

        void setTotalTime(long totalTimeMs) {
            if (totalTimeMs <= 0) {
                mTotalTime.setVisibility(View.GONE);
                mProgressBar.setVisibility(View.GONE);
            } else {
                mTotalTime.setVisibility(View.VISIBLE);
                mProgressBar.setVisibility(View.VISIBLE);
                mTotalTimeInMs = totalTimeMs;
                formatTime(totalTimeMs / 1000, mTotalTimeStringBuilder);
                mTotalTime.setText(mTotalTimeStringBuilder.toString());
                mProgressBar.setMax(Integer.MAX_VALUE);//current progress will be a fraction of this
            }
        }

        long getTotalTime() {
            return mTotalTimeInMs;
        }

        void setCurrentTime(long currentTimeMs) {
            long seconds = currentTimeMs / 1000;
            if (currentTimeMs != mCurrentTimeInMs) {
                mCurrentTimeInMs = currentTimeMs;
                formatTime(seconds, mCurrentTimeStringBuilder);
                mCurrentTime.setText(mCurrentTimeStringBuilder.toString());
            }
            // Use ratio to represent current progres
            double ratio = (double) mCurrentTimeInMs / mTotalTimeInMs;     // Range: [0, 1]
            double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
            mProgressBar.setProgress((int)progressRatio);
        }

        long getCurrentTime() {
            return mTotalTimeInMs;
        }

        void setSecondaryProgress(long progressMs) {
            mSecondaryProgressInMs = progressMs;
            // Solve the progress bar by using ratio
            double ratio = (double) progressMs / mTotalTimeInMs;           // Range: [0, 1]
            double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
            mProgressBar.setSecondaryProgress((int) progressRatio);
        }

        long getSecondaryProgress() {
            return mSecondaryProgressInMs;
        }
    }

    static void formatTime(long seconds, StringBuilder sb) {
        long minutes = seconds / 60;
        long hours = minutes / 60;
        seconds -= minutes * 60;
        minutes -= hours * 60;

        sb.setLength(0);
        if (hours > 0) {
            sb.append(hours).append(':');
            if (minutes < 10) {
                sb.append('0');
            }
        }
        sb.append(minutes).append(':');
        if (seconds < 10) {
            sb.append('0');
        }
        sb.append(seconds);
    }

    private boolean mMoreActionsEnabled = true;
    private static int sChildMarginBigger;
    private static int sChildMarginBiggest;

    /**
     * Constructor for a PlaybackControlsRowPresenter.
     *
     * @param layoutResourceId The resource id of the layout for this presenter.
     */
    public PlaybackControlsPresenter(int layoutResourceId) {
        super(layoutResourceId);
    }

    /**
     * Enables the display of secondary actions.
     * A "more actions" button will be displayed.  When "more actions" is selected,
     * the primary actions are replaced with the secondary actions.
     */
    public void enableSecondaryActions(boolean enable) {
        mMoreActionsEnabled = enable;
    }

    /**
     * Returns true if secondary actions are enabled.
     */
    public boolean areMoreActionsEnabled() {
        return mMoreActionsEnabled;
    }

    public void setProgressColor(ViewHolder vh, @ColorInt int color) {
        Drawable drawable = new ClipDrawable(new ColorDrawable(color),
                Gravity.LEFT, ClipDrawable.HORIZONTAL);
        ((LayerDrawable) vh.mProgressBar.getProgressDrawable())
                .setDrawableByLayerId(android.R.id.progress, drawable);
    }

    public void setTotalTime(ViewHolder vh, int ms) {
        setTotalTimeLong(vh, (long) ms);
    }

    public void setTotalTimeLong(ViewHolder vh, long ms) {
        vh.setTotalTime(ms);
    }

    public int getTotalTime(ViewHolder vh) {
        return MathUtil.safeLongToInt(getTotalTimeLong(vh));
    }

    public long getTotalTimeLong(ViewHolder vh) {
        return vh.getTotalTime();
    }

    public void setCurrentTime(ViewHolder vh, int ms) {
        setCurrentTimeLong(vh, (long) ms);
    }

    public void setCurrentTimeLong(ViewHolder vh, long ms) {
        vh.setCurrentTime(ms);
    }

    public int getCurrentTime(ViewHolder vh) {
        return MathUtil.safeLongToInt(getCurrentTimeLong(vh));
    }

    public long getCurrentTimeLong(ViewHolder vh) {
        return vh.getCurrentTime();
    }

    public void setSecondaryProgress(ViewHolder vh, int progressMs) {
        setSecondaryProgressLong(vh, (long) progressMs);
    }

    public void setSecondaryProgressLong(ViewHolder vh, long progressMs) {
        vh.setSecondaryProgress(progressMs);
    }

    public int getSecondaryProgress(ViewHolder vh) {
        return MathUtil.safeLongToInt(getSecondaryProgressLong(vh));
    }

    public long getSecondaryProgressLong(ViewHolder vh) {
        return vh.getSecondaryProgress();
    }

    public void showPrimaryActions(ViewHolder vh) {
        if (vh.mMoreActionsShowing) {
            vh.toggleMoreActions();
        }
    }

    public void resetFocus(ViewHolder vh) {
        vh.mControlBar.requestFocus();
    }

    public void enableTimeMargins(ViewHolder vh, boolean enable) {
        MarginLayoutParams lp;
        lp = (MarginLayoutParams) vh.mCurrentTime.getLayoutParams();
        lp.setMarginStart(enable ? vh.mCurrentTimeMarginStart : 0);
        vh.mCurrentTime.setLayoutParams(lp);

        lp = (MarginLayoutParams) vh.mTotalTime.getLayoutParams();
        lp.setMarginEnd(enable ? vh.mTotalTimeMarginEnd : 0);
        vh.mTotalTime.setLayoutParams(lp);
    }

    @Override
    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
        View v = LayoutInflater.from(parent.getContext())
            .inflate(getLayoutResourceId(), parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
        ViewHolder vh = (ViewHolder) holder;
        BoundData data = (BoundData) item;

        // If binding to a new adapter, display primary actions.
        if (vh.mMoreActionsAdapter != data.secondaryActionsAdapter) {
            vh.mMoreActionsAdapter = data.secondaryActionsAdapter;
            vh.mMoreActionsAdapter.registerObserver(vh.mMoreActionsObserver);
            vh.mMoreActionsShowing = false;
        }

        super.onBindViewHolder(holder, item);
        vh.showMoreActions(mMoreActionsEnabled);
    }

    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
        super.onUnbindViewHolder(holder);
        ViewHolder vh = (ViewHolder) holder;
        if (vh.mMoreActionsAdapter != null) {
            vh.mMoreActionsAdapter.unregisterObserver(vh.mMoreActionsObserver);
            vh.mMoreActionsAdapter = null;
        }
    }

    int getChildMarginBigger(Context context) {
        if (sChildMarginBigger == 0) {
            sChildMarginBigger = context.getResources().getDimensionPixelSize(
                    R.dimen.lb_playback_controls_child_margin_bigger);
        }
        return sChildMarginBigger;
    }

    int getChildMarginBiggest(Context context) {
        if (sChildMarginBiggest == 0) {
            sChildMarginBiggest = context.getResources().getDimensionPixelSize(
                    R.dimen.lb_playback_controls_child_margin_biggest);
        }
        return sChildMarginBiggest;
    }
}