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.Color;
18import android.support.annotation.ColorInt;
19import android.support.v17.leanback.R;
20import android.support.v17.leanback.widget.ControlBarPresenter.OnControlClickedListener;
21import android.support.v17.leanback.widget.ControlBarPresenter.OnControlSelectedListener;
22import android.util.TypedValue;
23import android.view.KeyEvent;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.ViewGroup.LayoutParams;
28import android.view.ViewGroup.MarginLayoutParams;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31
32/**
33 * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
34 * series of playback control buttons. Typically this row will be the first row in a fragment
35 * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment}.
36 *
37 * <p>The detailed description is rendered using a {@link Presenter} passed in
38 * {@link #PlaybackControlsRowPresenter(Presenter)}.  Typically this will be an instance of
39 * {@link AbstractDetailsDescriptionPresenter}.  The application can access the
40 * detailed description ViewHolder from {@link ViewHolder#mDescriptionViewHolder}.
41 * </p>
42 */
43public class PlaybackControlsRowPresenter extends PlaybackRowPresenter {
44
45    static class BoundData extends PlaybackControlsPresenter.BoundData {
46        ViewHolder mRowViewHolder;
47    }
48
49    /**
50     * A ViewHolder for the PlaybackControlsRow.
51     */
52    public class ViewHolder extends PlaybackRowPresenter.ViewHolder {
53        public final Presenter.ViewHolder mDescriptionViewHolder;
54        final ViewGroup mCard;
55        final ViewGroup mCardRightPanel;
56        final ImageView mImageView;
57        final ViewGroup mDescriptionDock;
58        final ViewGroup mControlsDock;
59        final ViewGroup mSecondaryControlsDock;
60        final View mSpacer;
61        final View mBottomSpacer;
62        View mBgView;
63        int mControlsDockMarginStart;
64        int mControlsDockMarginEnd;
65        PlaybackControlsPresenter.ViewHolder mControlsVh;
66        Presenter.ViewHolder mSecondaryControlsVh;
67        BoundData mControlsBoundData = new BoundData();
68        BoundData mSecondaryBoundData = new BoundData();
69        Presenter.ViewHolder mSelectedViewHolder;
70        Object mSelectedItem;
71        final PlaybackControlsRow.OnPlaybackProgressCallback mListener =
72                new PlaybackControlsRow.OnPlaybackProgressCallback() {
73            @Override
74            public void onCurrentPositionChanged(PlaybackControlsRow row, long ms) {
75                mPlaybackControlsPresenter.setCurrentTimeLong(mControlsVh, ms);
76            }
77
78            @Override
79            public void onDurationChanged(PlaybackControlsRow row, long ms) {
80                mPlaybackControlsPresenter.setTotalTimeLong(mControlsVh, ms);
81            }
82
83            @Override
84            public void onBufferedPositionChanged(PlaybackControlsRow row, long ms) {
85                mPlaybackControlsPresenter.setSecondaryProgressLong(mControlsVh, ms);
86            }
87        };
88
89        ViewHolder(View rootView, Presenter descriptionPresenter) {
90            super(rootView);
91            mCard = (ViewGroup) rootView.findViewById(R.id.controls_card);
92            mCardRightPanel = (ViewGroup) rootView.findViewById(R.id.controls_card_right_panel);
93            mImageView = (ImageView) rootView.findViewById(R.id.image);
94            mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock);
95            mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock);
96            mSecondaryControlsDock =
97                    (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock);
98            mSpacer = rootView.findViewById(R.id.spacer);
99            mBottomSpacer = rootView.findViewById(R.id.bottom_spacer);
100            mDescriptionViewHolder = descriptionPresenter == null ? null :
101                    descriptionPresenter.onCreateViewHolder(mDescriptionDock);
102            if (mDescriptionViewHolder != null) {
103                mDescriptionDock.addView(mDescriptionViewHolder.view);
104            }
105        }
106
107        void dispatchItemSelection() {
108            if (!isSelected()) {
109                return;
110            }
111            if (mSelectedViewHolder == null) {
112                if (getOnItemViewSelectedListener() != null) {
113                    getOnItemViewSelectedListener().onItemSelected(null, null,
114                            ViewHolder.this, getRow());
115                }
116            } else {
117                if (getOnItemViewSelectedListener() != null) {
118                    getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, mSelectedItem,
119                            ViewHolder.this, getRow());
120                }
121            }
122        };
123
124        Presenter getPresenter(boolean primary) {
125            ObjectAdapter adapter = primary
126                    ? ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter()
127                    : ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
128            if (adapter == null) {
129                return null;
130            }
131            if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
132                ControlButtonPresenterSelector selector =
133                        (ControlButtonPresenterSelector) adapter.getPresenterSelector();
134                return primary ? selector.getPrimaryPresenter()
135                        : selector.getSecondaryPresenter();
136            }
137            return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null);
138        }
139
140        void setOutline(View view) {
141            if (mBgView != null) {
142                RoundedRectHelper.getInstance().setClipToRoundedOutline(mBgView, false);
143                ShadowHelper.getInstance().setZ(mBgView, 0);
144            }
145            mBgView = view;
146            RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
147
148            if (sShadowZ == 0) {
149                sShadowZ = view.getResources().getDimensionPixelSize(
150                        R.dimen.lb_playback_controls_z);
151            }
152            ShadowHelper.getInstance().setZ(view, sShadowZ);
153        }
154    }
155
156    private int mBackgroundColor = Color.TRANSPARENT;
157    private boolean mBackgroundColorSet;
158    private int mProgressColor = Color.TRANSPARENT;
159    private boolean mProgressColorSet;
160    private boolean mSecondaryActionsHidden;
161    private Presenter mDescriptionPresenter;
162    PlaybackControlsPresenter mPlaybackControlsPresenter;
163    private ControlBarPresenter mSecondaryControlsPresenter;
164    OnActionClickedListener mOnActionClickedListener;
165    static float sShadowZ;
166
167    private final OnControlSelectedListener mOnControlSelectedListener =
168            new OnControlSelectedListener() {
169        @Override
170        public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item,
171                ControlBarPresenter.BoundData data) {
172            ViewHolder vh = ((BoundData) data).mRowViewHolder;
173            if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) {
174                vh.mSelectedViewHolder = itemViewHolder;
175                vh.mSelectedItem = item;
176                vh.dispatchItemSelection();
177            }
178        }
179    };
180
181    private final OnControlClickedListener mOnControlClickedListener =
182            new OnControlClickedListener() {
183        @Override
184        public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item,
185                ControlBarPresenter.BoundData data) {
186            ViewHolder vh = ((BoundData) data).mRowViewHolder;
187            if (vh.getOnItemViewClickedListener() != null) {
188                vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
189                        vh, vh.getRow());
190            }
191            if (mOnActionClickedListener != null && item instanceof Action) {
192                mOnActionClickedListener.onActionClicked((Action) item);
193            }
194        }
195    };
196
197    /**
198     * Constructor for a PlaybackControlsRowPresenter.
199     *
200     * @param descriptionPresenter Presenter for displaying item details.
201     */
202    public PlaybackControlsRowPresenter(Presenter descriptionPresenter) {
203        setHeaderPresenter(null);
204        setSelectEffectEnabled(false);
205
206        mDescriptionPresenter = descriptionPresenter;
207        mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls);
208        mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar);
209
210        mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
211        mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
212        mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
213        mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
214    }
215
216    /**
217     * Constructor for a PlaybackControlsRowPresenter.
218     */
219    public PlaybackControlsRowPresenter() {
220        this(null);
221    }
222
223    /**
224     * Sets the listener for {@link Action} click events.
225     */
226    public void setOnActionClickedListener(OnActionClickedListener listener) {
227        mOnActionClickedListener = listener;
228    }
229
230    /**
231     * Returns the listener for {@link Action} click events.
232     */
233    public OnActionClickedListener getOnActionClickedListener() {
234        return mOnActionClickedListener;
235    }
236
237    /**
238     * Sets the background color.  If not set, a default from the theme will be used.
239     */
240    public void setBackgroundColor(@ColorInt int color) {
241        mBackgroundColor = color;
242        mBackgroundColorSet = true;
243    }
244
245    /**
246     * Returns the background color.  If no background color was set, transparent
247     * is returned.
248     */
249    @ColorInt
250    public int getBackgroundColor() {
251        return mBackgroundColor;
252    }
253
254    /**
255     * Sets the primary color for the progress bar.  If not set, a default from
256     * the theme will be used.
257     */
258    public void setProgressColor(@ColorInt int color) {
259        mProgressColor = color;
260        mProgressColorSet = true;
261    }
262
263    /**
264     * Returns the primary color for the progress bar.  If no color was set, transparent
265     * is returned.
266     */
267    @ColorInt
268    public int getProgressColor() {
269        return mProgressColor;
270    }
271
272    /**
273     * Sets the secondary actions to be hidden behind a "more actions" button.
274     * When "more actions" is selected, the primary actions are replaced with
275     * the secondary actions.
276     */
277    public void setSecondaryActionsHidden(boolean hidden) {
278        mSecondaryActionsHidden = hidden;
279    }
280
281    /**
282     * Returns true if secondary actions are hidden.
283     */
284    public boolean areSecondaryActionsHidden() {
285        return mSecondaryActionsHidden;
286    }
287
288    /**
289     * Shows or hides space at the bottom of the playback controls row.
290     * This allows the row to hug the bottom of the display when no
291     * other rows are present.
292     */
293    public void showBottomSpace(ViewHolder vh, boolean show) {
294        vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE);
295    }
296
297    /**
298     * Displays the primary actions.  This will override the user having selected "more actions"
299     * to display the secondary actions; see {@link #setSecondaryActionsHidden(boolean)}.
300     */
301    public void showPrimaryActions(ViewHolder vh) {
302        mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh);
303        if (vh.view.hasFocus()) {
304            mPlaybackControlsPresenter.resetFocus(vh.mControlsVh);
305        }
306    }
307
308    @Override
309    public void onReappear(RowPresenter.ViewHolder rowViewHolder) {
310        showPrimaryActions((ViewHolder) rowViewHolder);
311    }
312
313    private int getDefaultBackgroundColor(Context context) {
314        TypedValue outValue = new TypedValue();
315        if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
316            return context.getResources().getColor(outValue.resourceId);
317        }
318        return context.getResources().getColor(R.color.lb_default_brand_color);
319    }
320
321    private int getDefaultProgressColor(Context context) {
322        TypedValue outValue = new TypedValue();
323        if (context.getTheme()
324                .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) {
325            return context.getResources().getColor(outValue.resourceId);
326        }
327        return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme);
328    }
329
330    @Override
331    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
332        View v = LayoutInflater.from(parent.getContext())
333            .inflate(R.layout.lb_playback_controls_row, parent, false);
334        ViewHolder vh = new ViewHolder(v, mDescriptionPresenter);
335        initRow(vh);
336        return vh;
337    }
338
339    private void initRow(final ViewHolder vh) {
340        MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
341        vh.mControlsDockMarginStart = lp.getMarginStart();
342        vh.mControlsDockMarginEnd = lp.getMarginEnd();
343
344        vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder)
345                mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock);
346        mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh, mProgressColorSet
347                ? mProgressColor : getDefaultProgressColor(vh.mControlsDock.getContext()));
348        mPlaybackControlsPresenter.setBackgroundColor(vh.mControlsVh, mBackgroundColorSet
349                ? mBackgroundColor : getDefaultBackgroundColor(vh.view.getContext()));
350        vh.mControlsDock.addView(vh.mControlsVh.view);
351
352        vh.mSecondaryControlsVh =
353                mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock);
354        if (!mSecondaryActionsHidden) {
355            vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
356        }
357        ((PlaybackControlsRowView) vh.view).setOnUnhandledKeyListener(
358                new PlaybackControlsRowView.OnUnhandledKeyListener() {
359            @Override
360            public boolean onUnhandledKey(KeyEvent event) {
361                if (vh.getOnKeyListener() != null) {
362                    if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
363                        return true;
364                    }
365                }
366                return false;
367            }
368        });
369    }
370
371    @Override
372    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
373        super.onBindRowViewHolder(holder, item);
374
375        ViewHolder vh = (ViewHolder) holder;
376        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
377
378        mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden);
379
380        if (row.getItem() == null) {
381            vh.mDescriptionDock.setVisibility(View.GONE);
382            vh.mSpacer.setVisibility(View.GONE);
383        } else {
384            vh.mDescriptionDock.setVisibility(View.VISIBLE);
385            if (vh.mDescriptionViewHolder != null) {
386                mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem());
387            }
388            vh.mSpacer.setVisibility(View.VISIBLE);
389        }
390
391        if (row.getImageDrawable() == null || row.getItem() == null) {
392            vh.mImageView.setImageDrawable(null);
393            updateCardLayout(vh, LayoutParams.WRAP_CONTENT);
394        } else {
395            vh.mImageView.setImageDrawable(row.getImageDrawable());
396            updateCardLayout(vh, vh.mImageView.getLayoutParams().height);
397        }
398
399        vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter();
400        vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter();
401        vh.mControlsBoundData.presenter = vh.getPresenter(true);
402        vh.mControlsBoundData.mRowViewHolder = vh;
403        mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData);
404
405        vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter();
406        vh.mSecondaryBoundData.presenter = vh.getPresenter(false);
407        vh.mSecondaryBoundData.mRowViewHolder = vh;
408        mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh,
409                vh.mSecondaryBoundData);
410
411        mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime());
412        mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime());
413        mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress());
414        row.setOnPlaybackProgressChangedListener(vh.mListener);
415    }
416
417    private void updateCardLayout(ViewHolder vh, int height) {
418        LayoutParams lp = vh.mCardRightPanel.getLayoutParams();
419        lp.height = height;
420        vh.mCardRightPanel.setLayoutParams(lp);
421
422        MarginLayoutParams mlp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
423        LinearLayout.LayoutParams llp =
424                (LinearLayout.LayoutParams) vh.mDescriptionDock.getLayoutParams();
425
426        if (height == LayoutParams.WRAP_CONTENT) {
427            llp.height = LayoutParams.WRAP_CONTENT;
428            mlp.setMarginStart(0);
429            mlp.setMarginEnd(0);
430            vh.mCard.setBackground(null);
431            vh.setOutline(vh.mControlsDock);
432            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true);
433        } else {
434            llp.height = 0;
435            llp.weight = 1;
436            mlp.setMarginStart(vh.mControlsDockMarginStart);
437            mlp.setMarginEnd(vh.mControlsDockMarginEnd);
438            vh.mCard.setBackgroundColor(mBackgroundColorSet ? mBackgroundColor :
439                    getDefaultBackgroundColor(vh.mCard.getContext()));
440            vh.setOutline(vh.mCard);
441            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false);
442        }
443        vh.mDescriptionDock.setLayoutParams(llp);
444        vh.mControlsDock.setLayoutParams(mlp);
445    }
446
447    @Override
448    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
449        ViewHolder vh = (ViewHolder) holder;
450        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
451
452        if (vh.mDescriptionViewHolder != null) {
453            mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder);
454        }
455        mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh);
456        mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh);
457        row.setOnPlaybackProgressChangedListener(null);
458
459        super.onUnbindRowViewHolder(holder);
460    }
461
462    @Override
463    protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
464        super.onRowViewSelected(vh, selected);
465        if (selected) {
466            ((ViewHolder) vh).dispatchItemSelection();
467        }
468    }
469
470    @Override
471    protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
472        super.onRowViewAttachedToWindow(vh);
473        if (mDescriptionPresenter != null) {
474            mDescriptionPresenter.onViewAttachedToWindow(
475                    ((ViewHolder) vh).mDescriptionViewHolder);
476        }
477    }
478
479    @Override
480    protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
481        super.onRowViewDetachedFromWindow(vh);
482        if (mDescriptionPresenter != null) {
483            mDescriptionPresenter.onViewDetachedFromWindow(
484                    ((ViewHolder) vh).mDescriptionViewHolder);
485        }
486    }
487}
488