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.content.Context;
17import android.graphics.drawable.ClipDrawable;
18import android.graphics.drawable.ColorDrawable;
19import android.graphics.drawable.Drawable;
20import android.graphics.drawable.LayerDrawable;
21import android.support.annotation.ColorInt;
22import android.support.v17.leanback.R;
23import android.support.v17.leanback.util.MathUtil;
24import android.view.Gravity;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.ViewGroup.MarginLayoutParams;
29import android.widget.FrameLayout;
30import android.widget.ProgressBar;
31import android.widget.TextView;
32
33/**
34 * A presenter for a control bar that supports "more actions",
35 * and toggling the set of controls between primary and secondary
36 * sets of {@link Actions}.
37 */
38class PlaybackControlsPresenter extends ControlBarPresenter {
39
40    /**
41     * The data type expected by this presenter.
42     */
43    static class BoundData extends ControlBarPresenter.BoundData {
44        /**
45         * The adapter containing secondary actions.
46         */
47        ObjectAdapter secondaryActionsAdapter;
48    }
49
50    class ViewHolder extends ControlBarPresenter.ViewHolder {
51        ObjectAdapter mMoreActionsAdapter;
52        ObjectAdapter.DataObserver mMoreActionsObserver;
53        final FrameLayout mMoreActionsDock;
54        Presenter.ViewHolder mMoreActionsViewHolder;
55        boolean mMoreActionsShowing;
56        final TextView mCurrentTime;
57        final TextView mTotalTime;
58        final ProgressBar mProgressBar;
59        long mCurrentTimeInMs = -1;         // Hold current time in milliseconds
60        long mTotalTimeInMs = -1;           // Hold total time in milliseconds
61        long mSecondaryProgressInMs = -1;   // Hold secondary progress in milliseconds
62        StringBuilder mTotalTimeStringBuilder = new StringBuilder();
63        StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
64        int mCurrentTimeMarginStart;
65        int mTotalTimeMarginEnd;
66
67        ViewHolder(View rootView) {
68            super(rootView);
69            mMoreActionsDock = (FrameLayout) rootView.findViewById(R.id.more_actions_dock);
70            mCurrentTime = (TextView) rootView.findViewById(R.id.current_time);
71            mTotalTime = (TextView) rootView.findViewById(R.id.total_time);
72            mProgressBar = (ProgressBar) rootView.findViewById(R.id.playback_progress);
73            mMoreActionsObserver = new ObjectAdapter.DataObserver() {
74                @Override
75                public void onChanged() {
76                    if (mMoreActionsShowing) {
77                        showControls(mPresenter);
78                    }
79                }
80                @Override
81                public void onItemRangeChanged(int positionStart, int itemCount) {
82                    if (mMoreActionsShowing) {
83                        for (int i = 0; i < itemCount; i++) {
84                            bindControlToAction(positionStart + i, mPresenter);
85                        }
86                    }
87                }
88            };
89            mCurrentTimeMarginStart =
90                    ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
91            mTotalTimeMarginEnd =
92                    ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
93        }
94
95        void showMoreActions(boolean show) {
96            if (show) {
97                if (mMoreActionsViewHolder == null) {
98                    Action action = new PlaybackControlsRow.MoreActions(mMoreActionsDock.getContext());
99                    mMoreActionsViewHolder = mPresenter.onCreateViewHolder(mMoreActionsDock);
100                    mPresenter.onBindViewHolder(mMoreActionsViewHolder, action);
101                    mPresenter.setOnClickListener(mMoreActionsViewHolder, new View.OnClickListener() {
102                        @Override
103                        public void onClick(View v) {
104                            toggleMoreActions();
105                        }
106                    });
107                }
108                if (mMoreActionsViewHolder.view.getParent() == null) {
109                    mMoreActionsDock.addView(mMoreActionsViewHolder.view);
110                }
111            } else if (mMoreActionsViewHolder != null
112                    && mMoreActionsViewHolder.view.getParent() != null) {
113                mMoreActionsDock.removeView(mMoreActionsViewHolder.view);
114            }
115        }
116
117        void toggleMoreActions() {
118            mMoreActionsShowing = !mMoreActionsShowing;
119            showControls(mPresenter);
120        }
121
122        @Override
123        ObjectAdapter getDisplayedAdapter() {
124            return mMoreActionsShowing ? mMoreActionsAdapter : mAdapter;
125        }
126
127        @Override
128        int getChildMarginFromCenter(Context context, int numControls) {
129            int margin = getControlIconWidth(context);
130            if (numControls < 4) {
131                margin += getChildMarginBiggest(context);
132            } else if (numControls < 6) {
133                margin += getChildMarginBigger(context);
134            } else {
135                margin += getChildMarginDefault(context);
136            }
137            return margin;
138        }
139
140        void setTotalTime(long totalTimeMs) {
141            if (totalTimeMs <= 0) {
142                mTotalTime.setVisibility(View.GONE);
143                mProgressBar.setVisibility(View.GONE);
144            } else {
145                mTotalTime.setVisibility(View.VISIBLE);
146                mProgressBar.setVisibility(View.VISIBLE);
147                mTotalTimeInMs = totalTimeMs;
148                formatTime(totalTimeMs / 1000, mTotalTimeStringBuilder);
149                mTotalTime.setText(mTotalTimeStringBuilder.toString());
150                mProgressBar.setMax(Integer.MAX_VALUE);//current progress will be a fraction of this
151            }
152        }
153
154        long getTotalTime() {
155            return mTotalTimeInMs;
156        }
157
158        void setCurrentTime(long currentTimeMs) {
159            long seconds = currentTimeMs / 1000;
160            if (currentTimeMs != mCurrentTimeInMs) {
161                mCurrentTimeInMs = currentTimeMs;
162                formatTime(seconds, mCurrentTimeStringBuilder);
163                mCurrentTime.setText(mCurrentTimeStringBuilder.toString());
164            }
165            // Use ratio to represent current progres
166            double ratio = (double) mCurrentTimeInMs / mTotalTimeInMs;     // Range: [0, 1]
167            double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
168            mProgressBar.setProgress((int)progressRatio);
169        }
170
171        long getCurrentTime() {
172            return mTotalTimeInMs;
173        }
174
175        void setSecondaryProgress(long progressMs) {
176            mSecondaryProgressInMs = progressMs;
177            // Solve the progress bar by using ratio
178            double ratio = (double) progressMs / mTotalTimeInMs;           // Range: [0, 1]
179            double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
180            mProgressBar.setSecondaryProgress((int) progressRatio);
181        }
182
183        long getSecondaryProgress() {
184            return mSecondaryProgressInMs;
185        }
186    }
187
188    static void formatTime(long seconds, StringBuilder sb) {
189        long minutes = seconds / 60;
190        long hours = minutes / 60;
191        seconds -= minutes * 60;
192        minutes -= hours * 60;
193
194        sb.setLength(0);
195        if (hours > 0) {
196            sb.append(hours).append(':');
197            if (minutes < 10) {
198                sb.append('0');
199            }
200        }
201        sb.append(minutes).append(':');
202        if (seconds < 10) {
203            sb.append('0');
204        }
205        sb.append(seconds);
206    }
207
208    private boolean mMoreActionsEnabled = true;
209    private static int sChildMarginBigger;
210    private static int sChildMarginBiggest;
211
212    /**
213     * Constructor for a PlaybackControlsRowPresenter.
214     *
215     * @param layoutResourceId The resource id of the layout for this presenter.
216     */
217    public PlaybackControlsPresenter(int layoutResourceId) {
218        super(layoutResourceId);
219    }
220
221    /**
222     * Enables the display of secondary actions.
223     * A "more actions" button will be displayed.  When "more actions" is selected,
224     * the primary actions are replaced with the secondary actions.
225     */
226    public void enableSecondaryActions(boolean enable) {
227        mMoreActionsEnabled = enable;
228    }
229
230    /**
231     * Returns true if secondary actions are enabled.
232     */
233    public boolean areMoreActionsEnabled() {
234        return mMoreActionsEnabled;
235    }
236
237    public void setProgressColor(ViewHolder vh, @ColorInt int color) {
238        Drawable drawable = new ClipDrawable(new ColorDrawable(color),
239                Gravity.LEFT, ClipDrawable.HORIZONTAL);
240        ((LayerDrawable) vh.mProgressBar.getProgressDrawable())
241                .setDrawableByLayerId(android.R.id.progress, drawable);
242    }
243
244    public void setTotalTime(ViewHolder vh, int ms) {
245        setTotalTimeLong(vh, (long) ms);
246    }
247
248    public void setTotalTimeLong(ViewHolder vh, long ms) {
249        vh.setTotalTime(ms);
250    }
251
252    public int getTotalTime(ViewHolder vh) {
253        return MathUtil.safeLongToInt(getTotalTimeLong(vh));
254    }
255
256    public long getTotalTimeLong(ViewHolder vh) {
257        return vh.getTotalTime();
258    }
259
260    public void setCurrentTime(ViewHolder vh, int ms) {
261        setCurrentTimeLong(vh, (long) ms);
262    }
263
264    public void setCurrentTimeLong(ViewHolder vh, long ms) {
265        vh.setCurrentTime(ms);
266    }
267
268    public int getCurrentTime(ViewHolder vh) {
269        return MathUtil.safeLongToInt(getCurrentTimeLong(vh));
270    }
271
272    public long getCurrentTimeLong(ViewHolder vh) {
273        return vh.getCurrentTime();
274    }
275
276    public void setSecondaryProgress(ViewHolder vh, int progressMs) {
277        setSecondaryProgressLong(vh, (long) progressMs);
278    }
279
280    public void setSecondaryProgressLong(ViewHolder vh, long progressMs) {
281        vh.setSecondaryProgress(progressMs);
282    }
283
284    public int getSecondaryProgress(ViewHolder vh) {
285        return MathUtil.safeLongToInt(getSecondaryProgressLong(vh));
286    }
287
288    public long getSecondaryProgressLong(ViewHolder vh) {
289        return vh.getSecondaryProgress();
290    }
291
292    public void showPrimaryActions(ViewHolder vh) {
293        if (vh.mMoreActionsShowing) {
294            vh.toggleMoreActions();
295        }
296    }
297
298    public void resetFocus(ViewHolder vh) {
299        vh.mControlBar.requestFocus();
300    }
301
302    public void enableTimeMargins(ViewHolder vh, boolean enable) {
303        MarginLayoutParams lp;
304        lp = (MarginLayoutParams) vh.mCurrentTime.getLayoutParams();
305        lp.setMarginStart(enable ? vh.mCurrentTimeMarginStart : 0);
306        vh.mCurrentTime.setLayoutParams(lp);
307
308        lp = (MarginLayoutParams) vh.mTotalTime.getLayoutParams();
309        lp.setMarginEnd(enable ? vh.mTotalTimeMarginEnd : 0);
310        vh.mTotalTime.setLayoutParams(lp);
311    }
312
313    @Override
314    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
315        View v = LayoutInflater.from(parent.getContext())
316            .inflate(getLayoutResourceId(), parent, false);
317        return new ViewHolder(v);
318    }
319
320    @Override
321    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
322        ViewHolder vh = (ViewHolder) holder;
323        BoundData data = (BoundData) item;
324
325        // If binding to a new adapter, display primary actions.
326        if (vh.mMoreActionsAdapter != data.secondaryActionsAdapter) {
327            vh.mMoreActionsAdapter = data.secondaryActionsAdapter;
328            vh.mMoreActionsAdapter.registerObserver(vh.mMoreActionsObserver);
329            vh.mMoreActionsShowing = false;
330        }
331
332        super.onBindViewHolder(holder, item);
333        vh.showMoreActions(mMoreActionsEnabled);
334    }
335
336    @Override
337    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
338        super.onUnbindViewHolder(holder);
339        ViewHolder vh = (ViewHolder) holder;
340        if (vh.mMoreActionsAdapter != null) {
341            vh.mMoreActionsAdapter.unregisterObserver(vh.mMoreActionsObserver);
342            vh.mMoreActionsAdapter = null;
343        }
344    }
345
346    int getChildMarginBigger(Context context) {
347        if (sChildMarginBigger == 0) {
348            sChildMarginBigger = context.getResources().getDimensionPixelSize(
349                    R.dimen.lb_playback_controls_child_margin_bigger);
350        }
351        return sChildMarginBigger;
352    }
353
354    int getChildMarginBiggest(Context context) {
355        if (sChildMarginBiggest == 0) {
356            sChildMarginBiggest = context.getResources().getDimensionPixelSize(
357                    R.dimen.lb_playback_controls_child_margin_biggest);
358        }
359        return sChildMarginBiggest;
360    }
361}
362