1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.animation.ValueAnimator;
17import android.content.Context;
18import android.content.res.Resources;
19import android.graphics.drawable.ClipDrawable;
20import android.graphics.drawable.ColorDrawable;
21import android.graphics.drawable.Drawable;
22import android.graphics.drawable.LayerDrawable;
23import android.support.annotation.ColorInt;
24import android.support.v17.leanback.R;
25import android.view.Gravity;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewGroup.MarginLayoutParams;
30import android.view.animation.LinearInterpolator;
31import android.widget.FrameLayout;
32import android.widget.ProgressBar;
33import android.widget.TextView;
34
35/**
36 * A presenter for a control bar that supports "more actions",
37 * and toggling the set of controls between primary and secondary
38 * sets of {@link Actions}.
39 */
40class PlaybackControlsPresenter extends ControlBarPresenter {
41
42    /**
43     * The data type expected by this presenter.
44     */
45    static class BoundData extends ControlBarPresenter.BoundData {
46        /**
47         * The adapter containing secondary actions.
48         */
49        ObjectAdapter secondaryActionsAdapter;
50    }
51
52    class ViewHolder extends ControlBarPresenter.ViewHolder {
53        ObjectAdapter mMoreActionsAdapter;
54        ObjectAdapter.DataObserver mMoreActionsObserver;
55        final FrameLayout mMoreActionsDock;
56        Presenter.ViewHolder mMoreActionsViewHolder;
57        boolean mMoreActionsShowing;
58        final TextView mCurrentTime;
59        final TextView mTotalTime;
60        final ProgressBar mProgressBar;
61        int mCurrentTimeInSeconds = -1;
62        StringBuilder mTotalTimeStringBuilder = new StringBuilder();
63        StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
64        int mCurrentTimeMarginStart;
65        int mTotalTimeMarginEnd;
66        final PersistentFocusWrapper mControlsFocusWrapper;
67
68        ViewHolder(View rootView) {
69            super(rootView);
70            mMoreActionsDock = (FrameLayout) rootView.findViewById(R.id.more_actions_dock);
71            mCurrentTime = (TextView) rootView.findViewById(R.id.current_time);
72            mTotalTime = (TextView) rootView.findViewById(R.id.total_time);
73            mProgressBar = (ProgressBar) rootView.findViewById(R.id.playback_progress);
74            mMoreActionsObserver = new ObjectAdapter.DataObserver() {
75                @Override
76                public void onChanged() {
77                    if (mMoreActionsShowing) {
78                        showControls(mPresenter);
79                    }
80                }
81                @Override
82                public void onItemRangeChanged(int positionStart, int itemCount) {
83                    if (mMoreActionsShowing) {
84                        for (int i = 0; i < itemCount; i++) {
85                            bindControlToAction(positionStart + i, mPresenter);
86                        }
87                    }
88                }
89            };
90            mCurrentTimeMarginStart =
91                    ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
92            mTotalTimeMarginEnd =
93                    ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
94            mControlsFocusWrapper = (PersistentFocusWrapper) mControlBar.getParent();
95        }
96
97        void showMoreActions(boolean show) {
98            if (show) {
99                if (mMoreActionsViewHolder == null) {
100                    Action action = new PlaybackControlsRow.MoreActions(mMoreActionsDock.getContext());
101                    mMoreActionsViewHolder = mPresenter.onCreateViewHolder(mMoreActionsDock);
102                    mPresenter.onBindViewHolder(mMoreActionsViewHolder, action);
103                    mPresenter.setOnClickListener(mMoreActionsViewHolder, new View.OnClickListener() {
104                        @Override
105                        public void onClick(View v) {
106                            toggleMoreActions();
107                        }
108                    });
109                }
110                if (mMoreActionsViewHolder.view.getParent() == null) {
111                    mMoreActionsDock.addView(mMoreActionsViewHolder.view);
112                }
113            } else if (mMoreActionsViewHolder != null &&
114                    mMoreActionsViewHolder.view.getParent() != null) {
115                mMoreActionsDock.removeView(mMoreActionsViewHolder.view);
116            }
117        }
118
119        void toggleMoreActions() {
120            mMoreActionsShowing = !mMoreActionsShowing;
121            showControls(mPresenter);
122        }
123
124        @Override
125        ObjectAdapter getDisplayedAdapter() {
126            return mMoreActionsShowing ? mMoreActionsAdapter : mAdapter;
127        }
128
129        @Override
130        int getChildMarginFromCenter(Context context, int numControls) {
131            int margin = getControlIconWidth(context);
132            if (numControls < 4) {
133                margin += getChildMarginBiggest(context);
134            } else if (numControls < 6) {
135                margin += getChildMarginBigger(context);
136            } else {
137                margin += getChildMarginDefault(context);
138            }
139            return margin;
140        }
141
142        void setTotalTime(int totalTimeMs) {
143            if (totalTimeMs <= 0) {
144                mTotalTime.setVisibility(View.GONE);
145                mProgressBar.setVisibility(View.GONE);
146            } else {
147                mTotalTime.setVisibility(View.VISIBLE);
148                mProgressBar.setVisibility(View.VISIBLE);
149                formatTime(totalTimeMs / 1000, mTotalTimeStringBuilder);
150                mTotalTime.setText(mTotalTimeStringBuilder.toString());
151                mProgressBar.setMax(totalTimeMs);
152            }
153        }
154
155        int getTotalTime() {
156            return mProgressBar.getMax();
157        }
158
159        void setCurrentTime(int currentTimeMs) {
160            int seconds = currentTimeMs / 1000;
161            if (seconds != mCurrentTimeInSeconds) {
162                mCurrentTimeInSeconds = seconds;
163                formatTime(mCurrentTimeInSeconds, mCurrentTimeStringBuilder);
164                mCurrentTime.setText(mCurrentTimeStringBuilder.toString());
165            }
166            mProgressBar.setProgress(currentTimeMs);
167        }
168
169        int getCurrentTime() {
170            return mProgressBar.getProgress();
171        }
172
173        void setSecondaryProgress(int progressMs) {
174            mProgressBar.setSecondaryProgress(progressMs);
175        }
176
177        int getSecondaryProgress() {
178            return mProgressBar.getSecondaryProgress();
179        }
180    }
181
182    private static void formatTime(int seconds, StringBuilder sb) {
183        int minutes = seconds / 60;
184        int hours = minutes / 60;
185        seconds -= minutes * 60;
186        minutes -= hours * 60;
187
188        sb.setLength(0);
189        if (hours > 0) {
190            sb.append(hours).append(':');
191            if (minutes < 10) {
192                sb.append('0');
193            }
194        }
195        sb.append(minutes).append(':');
196        if (seconds < 10) {
197            sb.append('0');
198        }
199        sb.append(seconds);
200    }
201
202    private boolean mMoreActionsEnabled = true;
203    private static int sChildMarginBigger;
204    private static int sChildMarginBiggest;
205
206    /**
207     * Constructor for a PlaybackControlsRowPresenter.
208     *
209     * @param layoutResourceId The resource id of the layout for this presenter.
210     */
211    public PlaybackControlsPresenter(int layoutResourceId) {
212        super(layoutResourceId);
213    }
214
215    /**
216     * Enables the display of secondary actions.
217     * A "more actions" button will be displayed.  When "more actions" is selected,
218     * the primary actions are replaced with the secondary actions.
219     */
220    public void enableSecondaryActions(boolean enable) {
221        mMoreActionsEnabled = enable;
222    }
223
224    /**
225     * Returns true if secondary actions are enabled.
226     */
227    public boolean areMoreActionsEnabled() {
228        return mMoreActionsEnabled;
229    }
230
231    public void setProgressColor(ViewHolder vh, @ColorInt int color) {
232        Drawable drawable = new ClipDrawable(new ColorDrawable(color),
233                Gravity.LEFT, ClipDrawable.HORIZONTAL);
234        ((LayerDrawable) vh.mProgressBar.getProgressDrawable())
235                .setDrawableByLayerId(android.R.id.progress, drawable);
236    }
237
238    public void setTotalTime(ViewHolder vh, int ms) {
239        vh.setTotalTime(ms);
240    }
241
242    public int getTotalTime(ViewHolder vh) {
243        return vh.getTotalTime();
244    }
245
246    public void setCurrentTime(ViewHolder vh, int ms) {
247        vh.setCurrentTime(ms);
248    }
249
250    public int getCurrentTime(ViewHolder vh) {
251        return vh.getCurrentTime();
252    }
253
254    public void setSecondaryProgress(ViewHolder vh, int progressMs) {
255        vh.setSecondaryProgress(progressMs);
256    }
257
258    public int getSecondaryProgress(ViewHolder vh) {
259        return vh.getSecondaryProgress();
260    }
261
262    public void showPrimaryActions(ViewHolder vh) {
263        if (vh.mMoreActionsShowing) {
264            vh.toggleMoreActions();
265        }
266    }
267
268    public void resetFocus(ViewHolder vh) {
269        vh.mControlsFocusWrapper.clearSelection();
270        vh.mControlBar.requestFocus();
271    }
272
273    public void enableTimeMargins(ViewHolder vh, boolean enable) {
274        MarginLayoutParams lp;
275        lp = (MarginLayoutParams) vh.mCurrentTime.getLayoutParams();
276        lp.setMarginStart(enable ? vh.mCurrentTimeMarginStart : 0);
277        vh.mCurrentTime.setLayoutParams(lp);
278
279        lp = (MarginLayoutParams) vh.mTotalTime.getLayoutParams();
280        lp.setMarginEnd(enable ? vh.mTotalTimeMarginEnd : 0);
281        vh.mTotalTime.setLayoutParams(lp);
282    }
283
284    @Override
285    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
286        View v = LayoutInflater.from(parent.getContext())
287            .inflate(getLayoutResourceId(), parent, false);
288        return new ViewHolder(v);
289    }
290
291    @Override
292    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
293        ViewHolder vh = (ViewHolder) holder;
294        BoundData data = (BoundData) item;
295
296        // If binding to a new adapter, display primary actions.
297        if (vh.mMoreActionsAdapter != data.secondaryActionsAdapter) {
298            vh.mMoreActionsAdapter = data.secondaryActionsAdapter;
299            vh.mMoreActionsAdapter.registerObserver(vh.mMoreActionsObserver);
300            vh.mMoreActionsShowing = false;
301        }
302
303        super.onBindViewHolder(holder, item);
304        vh.showMoreActions(mMoreActionsEnabled);
305    }
306
307    @Override
308    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
309        super.onUnbindViewHolder(holder);
310        ViewHolder vh = (ViewHolder) holder;
311        if (vh.mMoreActionsAdapter != null) {
312            vh.mMoreActionsAdapter.unregisterObserver(vh.mMoreActionsObserver);
313            vh.mMoreActionsAdapter = null;
314        }
315    }
316
317    int getChildMarginBigger(Context context) {
318        if (sChildMarginBigger == 0) {
319            sChildMarginBigger = context.getResources().getDimensionPixelSize(
320                    R.dimen.lb_playback_controls_child_margin_bigger);
321        }
322        return sChildMarginBigger;
323    }
324
325    int getChildMarginBiggest(Context context) {
326        if (sChildMarginBiggest == 0) {
327            sChildMarginBiggest = context.getResources().getDimensionPixelSize(
328                    R.dimen.lb_playback_controls_child_margin_biggest);
329        }
330        return sChildMarginBiggest;
331    }
332}
333