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.content.res.TypedArray;
18import android.support.v17.leanback.R;
19import android.support.v17.leanback.system.Settings;
20import android.support.v17.leanback.transition.TransitionHelper;
21import android.support.v7.widget.RecyclerView;
22import android.util.Log;
23import android.view.KeyEvent;
24import android.view.View;
25import android.view.ViewGroup;
26
27import java.util.HashMap;
28
29/**
30 * ListRowPresenter renders {@link ListRow} using a
31 * {@link HorizontalGridView} hosted in a {@link ListRowView}.
32 *
33 * <h3>Hover card</h3>
34 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
35 * display a view for the currently focused list item below the rendered
36 * list. This view is known as a hover card.
37 *
38 * <h3>Selection animation</h3>
39 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draws
40 * a dim overlay on each view individually.  A subclass may override and disable
41 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
42 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
43 *
44 * <h3>Shadow</h3>
45 * ListRowPresenter applies a default shadow to each child view.  Call
46 * {@link #setShadowEnabled(boolean)} to disable shadows.  A subclass may override and return
47 * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation.
48 */
49public class ListRowPresenter extends RowPresenter {
50
51    private static final String TAG = "ListRowPresenter";
52    private static final boolean DEBUG = false;
53
54    private static final int DEFAULT_RECYCLED_POOL_SIZE = 24;
55
56    /**
57     * ViewHolder for the ListRowPresenter.
58     */
59    public static class ViewHolder extends RowPresenter.ViewHolder {
60        final ListRowPresenter mListRowPresenter;
61        final HorizontalGridView mGridView;
62        ItemBridgeAdapter mItemBridgeAdapter;
63        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
64        final int mPaddingTop;
65        final int mPaddingBottom;
66        final int mPaddingLeft;
67        final int mPaddingRight;
68
69        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
70            super(rootView);
71            mGridView = gridView;
72            mListRowPresenter = p;
73            mPaddingTop = mGridView.getPaddingTop();
74            mPaddingBottom = mGridView.getPaddingBottom();
75            mPaddingLeft = mGridView.getPaddingLeft();
76            mPaddingRight = mGridView.getPaddingRight();
77        }
78
79        /**
80         * Gets ListRowPresenter that creates this ViewHolder.
81         * @return ListRowPresenter that creates this ViewHolder.
82         */
83        public final ListRowPresenter getListRowPresenter() {
84            return mListRowPresenter;
85        }
86
87        /**
88         * Gets HorizontalGridView that shows a list of items.
89         * @return HorizontalGridView that shows a list of items.
90         */
91        public final HorizontalGridView getGridView() {
92            return mGridView;
93        }
94
95        /**
96         * Gets ItemBridgeAdapter that creates the list of items.
97         * @return ItemBridgeAdapter that creates the list of items.
98         */
99        public final ItemBridgeAdapter getBridgeAdapter() {
100            return mItemBridgeAdapter;
101        }
102
103        /**
104         * Gets selected item position in adapter.
105         * @return Selected item position in adapter.
106         */
107        public int getSelectedPosition() {
108            return mGridView.getSelectedPosition();
109        }
110
111        /**
112         * Gets ViewHolder at a position in adapter.  Returns null if the item does not exist
113         * or the item is not bound to a view.
114         * @param position Position of the item in adapter.
115         * @return ViewHolder bounds to the item.
116         */
117        public Presenter.ViewHolder getItemViewHolder(int position) {
118            ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView
119                    .findViewHolderForAdapterPosition(position);
120            if (ibvh == null) {
121                return null;
122            }
123            return ibvh.getViewHolder();
124        }
125    }
126
127    /**
128     * A task on the ListRowPresenter.ViewHolder that can select an item by position in the
129     * HorizontalGridView and perform an optional item task on it.
130     */
131    public static class SelectItemViewHolderTask extends Presenter.ViewHolderTask {
132
133        private int mItemPosition;
134        private boolean mSmoothScroll = true;
135        Presenter.ViewHolderTask mItemTask;
136
137        public SelectItemViewHolderTask(int itemPosition) {
138            setItemPosition(itemPosition);
139        }
140
141        /**
142         * Sets the adapter position of item to select.
143         * @param itemPosition Position of the item in adapter.
144         */
145        public void setItemPosition(int itemPosition) {
146            mItemPosition = itemPosition;
147        }
148
149        /**
150         * Returns the adapter position of item to select.
151         * @return The adapter position of item to select.
152         */
153        public int getItemPosition() {
154            return mItemPosition;
155        }
156
157        /**
158         * Sets smooth scrolling to the item or jump to the item without scrolling.  By default it is
159         * true.
160         * @param smoothScroll True for smooth scrolling to the item, false otherwise.
161         */
162        public void setSmoothScroll(boolean smoothScroll) {
163            mSmoothScroll = smoothScroll;
164        }
165
166        /**
167         * Returns true if smooth scrolling to the item false otherwise.  By default it is true.
168         * @return True for smooth scrolling to the item, false otherwise.
169         */
170        public boolean isSmoothScroll() {
171            return mSmoothScroll;
172        }
173
174        /**
175         * Returns optional task to run when the item is selected, null for no task.
176         * @return Optional task to run when the item is selected, null for no task.
177         */
178        public Presenter.ViewHolderTask getItemTask() {
179            return mItemTask;
180        }
181
182        /**
183         * Sets task to run when the item is selected, null for no task.
184         * @param itemTask Optional task to run when the item is selected, null for no task.
185         */
186        public void setItemTask(Presenter.ViewHolderTask itemTask) {
187            mItemTask = itemTask;
188        }
189
190        @Override
191        public void run(Presenter.ViewHolder holder) {
192            if (holder instanceof ListRowPresenter.ViewHolder) {
193                HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) holder).getGridView();
194                android.support.v17.leanback.widget.ViewHolderTask task = null;
195                if (mItemTask != null) {
196                    task = new android.support.v17.leanback.widget.ViewHolderTask() {
197                        final Presenter.ViewHolderTask itemTask = mItemTask;
198                        @Override
199                        public void run(RecyclerView.ViewHolder rvh) {
200                            ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) rvh;
201                            itemTask.run(ibvh.getViewHolder());
202                        }
203                    };
204                }
205                if (isSmoothScroll()) {
206                    gridView.setSelectedPositionSmooth(mItemPosition, task);
207                } else {
208                    gridView.setSelectedPosition(mItemPosition, task);
209                }
210            }
211        }
212    }
213
214    class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter {
215        ListRowPresenter.ViewHolder mRowViewHolder;
216
217        ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
218            mRowViewHolder = rowViewHolder;
219        }
220
221        @Override
222        protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
223            if (viewHolder.itemView instanceof ViewGroup) {
224                TransitionHelper.setTransitionGroup((ViewGroup) viewHolder.itemView, true);
225            }
226            if (mShadowOverlayHelper != null) {
227                mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
228            }
229        }
230
231        @Override
232        public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
233            // Only when having an OnItemClickListner, we will attach the OnClickListener.
234            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
235                viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
236                    @Override
237                    public void onClick(View v) {
238                        ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
239                                mRowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
240                        if (mRowViewHolder.getOnItemViewClickedListener() != null) {
241                            mRowViewHolder.getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
242                                    ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow);
243                        }
244                    }
245                });
246            }
247        }
248
249        @Override
250        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
251            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
252                viewHolder.mHolder.view.setOnClickListener(null);
253            }
254        }
255
256        @Override
257        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
258            if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
259                int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor();
260                mShadowOverlayHelper.setOverlayColor(viewHolder.itemView, dimmedColor);
261            }
262            mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
263        }
264
265        @Override
266        public void onAddPresenter(Presenter presenter, int type) {
267            mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
268                    type, getRecycledPoolSize(presenter));
269        }
270    }
271
272    private int mNumRows = 1;
273    private int mRowHeight;
274    private int mExpandedRowHeight;
275    private PresenterSelector mHoverCardPresenterSelector;
276    private int mFocusZoomFactor;
277    private boolean mUseFocusDimmer;
278    private boolean mShadowEnabled = true;
279    private int mBrowseRowsFadingEdgeLength = -1;
280    private boolean mRoundedCornersEnabled = true;
281    private boolean mKeepChildForeground = true;
282    private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
283    ShadowOverlayHelper mShadowOverlayHelper;
284    private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;
285
286    private static int sSelectedRowTopPadding;
287    private static int sExpandedSelectedRowTopPadding;
288    private static int sExpandedRowNoHovercardBottomPadding;
289
290    /**
291     * Constructs a ListRowPresenter with defaults.
292     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and
293     * disabled dimming on focus.
294     */
295    public ListRowPresenter() {
296        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
297    }
298
299    /**
300     * Constructs a ListRowPresenter with the given parameters.
301     *
302     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
303     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
304     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
305     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
306     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
307     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
308     * Dimming on focus defaults to disabled.
309     */
310    public ListRowPresenter(int focusZoomFactor) {
311        this(focusZoomFactor, false);
312    }
313
314    /**
315     * Constructs a ListRowPresenter with the given parameters.
316     *
317     * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of
318     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
319     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
320     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
321     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
322     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
323     * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer
324     */
325    public ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer) {
326        if (!FocusHighlightHelper.isValidZoomIndex(focusZoomFactor)) {
327            throw new IllegalArgumentException("Unhandled zoom factor");
328        }
329        mFocusZoomFactor = focusZoomFactor;
330        mUseFocusDimmer = useFocusDimmer;
331    }
332
333    /**
334     * Sets the row height for rows created by this Presenter. Rows
335     * created before calling this method will not be updated.
336     *
337     * @param rowHeight Row height in pixels, or WRAP_CONTENT, or 0
338     * to use the default height.
339     */
340    public void setRowHeight(int rowHeight) {
341        mRowHeight = rowHeight;
342    }
343
344    /**
345     * Returns the row height for list rows created by this Presenter.
346     */
347    public int getRowHeight() {
348        return mRowHeight;
349    }
350
351    /**
352     * Sets the expanded row height for rows created by this Presenter.
353     * If not set, expanded rows have the same height as unexpanded
354     * rows.
355     *
356     * @param rowHeight The row height in to use when the row is expanded,
357     *        in pixels, or WRAP_CONTENT, or 0 to use the default.
358     */
359    public void setExpandedRowHeight(int rowHeight) {
360        mExpandedRowHeight = rowHeight;
361    }
362
363    /**
364     * Returns the expanded row height for rows created by this Presenter.
365     */
366    public int getExpandedRowHeight() {
367        return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight;
368    }
369
370    /**
371     * Returns the zoom factor used for focus highlighting.
372     */
373    public final int getFocusZoomFactor() {
374        return mFocusZoomFactor;
375    }
376
377    /**
378     * Returns the zoom factor used for focus highlighting.
379     * @deprecated use {@link #getFocusZoomFactor} instead.
380     */
381    @Deprecated
382    public final int getZoomFactor() {
383        return mFocusZoomFactor;
384    }
385
386    /**
387     * Returns true if the focus dimmer is used for focus highlighting; false otherwise.
388     */
389    public final boolean isFocusDimmerUsed() {
390        return mUseFocusDimmer;
391    }
392
393    /**
394     * Sets the numbers of rows for rendering the list of items. By default, it is
395     * set to 1.
396     */
397    public void setNumRows(int numRows) {
398        this.mNumRows = numRows;
399    }
400
401    @Override
402    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
403        super.initializeRowViewHolder(holder);
404        final ViewHolder rowViewHolder = (ViewHolder) holder;
405        Context context = holder.view.getContext();
406        if (mShadowOverlayHelper == null) {
407            mShadowOverlayHelper = new ShadowOverlayHelper.Builder()
408                    .needsOverlay(needsDefaultListSelectEffect())
409                    .needsShadow(needsDefaultShadow())
410                    .needsRoundedCorner(areChildRoundedCornersEnabled())
411                    .preferZOrder(isUsingZOrder(context))
412                    .keepForegroundDrawable(mKeepChildForeground)
413                    .options(createShadowOverlayOptions())
414                    .build(context);
415            if (mShadowOverlayHelper.needsWrapper()) {
416                mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper(
417                        mShadowOverlayHelper);
418            }
419        }
420        rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
421        // set wrapper if needed
422        rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper);
423        mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView);
424
425        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
426                mFocusZoomFactor, mUseFocusDimmer);
427        rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
428                != ShadowOverlayHelper.SHADOW_DYNAMIC);
429        rowViewHolder.mGridView.setOnChildSelectedListener(
430                new OnChildSelectedListener() {
431            @Override
432            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
433                selectChildView(rowViewHolder, view, true);
434            }
435        });
436        rowViewHolder.mGridView.setOnUnhandledKeyListener(
437                new BaseGridView.OnUnhandledKeyListener() {
438            @Override
439            public boolean onUnhandledKey(KeyEvent event) {
440                return rowViewHolder.getOnKeyListener() != null &&
441                        rowViewHolder.getOnKeyListener().onKey(
442                                rowViewHolder.view, event.getKeyCode(), event);
443            }
444        });
445        rowViewHolder.mGridView.setNumRows(mNumRows);
446    }
447
448    final boolean needsDefaultListSelectEffect() {
449        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
450    }
451
452    /**
453     * Sets the recycled pool size for the given presenter.
454     */
455    public void setRecycledPoolSize(Presenter presenter, int size) {
456        mRecycledPoolSize.put(presenter, size);
457    }
458
459    /**
460     * Returns the recycled pool size for the given presenter.
461     */
462    public int getRecycledPoolSize(Presenter presenter) {
463        return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) :
464                DEFAULT_RECYCLED_POOL_SIZE;
465    }
466
467    /**
468     * Sets the {@link PresenterSelector} used for showing a select object in a hover card.
469     */
470    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
471        mHoverCardPresenterSelector = selector;
472    }
473
474    /**
475     * Returns the {@link PresenterSelector} used for showing a select object in a hover card.
476     */
477    public final PresenterSelector getHoverCardPresenterSelector() {
478        return mHoverCardPresenterSelector;
479    }
480
481    /*
482     * Perform operations when a child of horizontal grid view is selected.
483     */
484    void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) {
485        if (view != null) {
486            if (rowViewHolder.mSelected) {
487                ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
488                        rowViewHolder.mGridView.getChildViewHolder(view);
489
490                if (mHoverCardPresenterSelector != null) {
491                    rowViewHolder.mHoverCardViewSwitcher.select(
492                            rowViewHolder.mGridView, view, ibh.mItem);
493                }
494                if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) {
495                    rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
496                            ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow);
497                }
498            }
499        } else {
500            if (mHoverCardPresenterSelector != null) {
501                rowViewHolder.mHoverCardViewSwitcher.unselect();
502            }
503            if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) {
504                rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
505                        null, null, rowViewHolder, rowViewHolder.mRow);
506            }
507        }
508    }
509
510    private static void initStatics(Context context) {
511        if (sSelectedRowTopPadding == 0) {
512            sSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
513                    R.dimen.lb_browse_selected_row_top_padding);
514            sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
515                    R.dimen.lb_browse_expanded_selected_row_top_padding);
516            sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize(
517                    R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding);
518        }
519    }
520
521    private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) {
522        RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder();
523        if (headerViewHolder != null) {
524            if (getHeaderPresenter() != null) {
525                return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder);
526            }
527            return headerViewHolder.view.getPaddingBottom();
528        }
529        return 0;
530    }
531
532    private void setVerticalPadding(ListRowPresenter.ViewHolder vh) {
533        int paddingTop, paddingBottom;
534        // Note: sufficient bottom padding needed for card shadows.
535        if (vh.isExpanded()) {
536            int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh);
537            if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline);
538            paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) -
539                    headerSpaceUnderBaseline;
540            paddingBottom = mHoverCardPresenterSelector == null ?
541                    sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom;
542        } else if (vh.isSelected()) {
543            paddingTop = sSelectedRowTopPadding - vh.mPaddingBottom;
544            paddingBottom = sSelectedRowTopPadding;
545        } else {
546            paddingTop = 0;
547            paddingBottom = vh.mPaddingBottom;
548        }
549        vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight,
550                paddingBottom);
551    }
552
553    @Override
554    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
555        initStatics(parent.getContext());
556        ListRowView rowView = new ListRowView(parent.getContext());
557        setupFadingEffect(rowView);
558        if (mRowHeight != 0) {
559            rowView.getGridView().setRowHeight(mRowHeight);
560        }
561        return new ViewHolder(rowView, rowView.getGridView(), this);
562    }
563
564    /**
565     * Dispatch item selected event using current selected item in the {@link HorizontalGridView}.
566     * The method should only be called from onRowViewSelected().
567     */
568    @Override
569    protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) {
570        ViewHolder vh = (ViewHolder)holder;
571        ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder)
572                vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition());
573        if (itemViewHolder == null) {
574            super.dispatchItemSelectedListener(holder, selected);
575            return;
576        }
577
578        if (selected) {
579            if (holder.getOnItemViewSelectedListener() != null) {
580                holder.getOnItemViewSelectedListener().onItemSelected(
581                        itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow());
582            }
583        }
584    }
585
586    @Override
587    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
588        super.onRowViewSelected(holder, selected);
589        ViewHolder vh = (ViewHolder) holder;
590        setVerticalPadding(vh);
591        updateFooterViewSwitcher(vh);
592    }
593
594    /*
595     * Show or hide hover card when row selection or expanded state is changed.
596     */
597    private void updateFooterViewSwitcher(ViewHolder vh) {
598        if (vh.mExpanded && vh.mSelected) {
599            if (mHoverCardPresenterSelector != null) {
600                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
601                        mHoverCardPresenterSelector);
602            }
603            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
604                    vh.mGridView.findViewHolderForPosition(
605                            vh.mGridView.getSelectedPosition());
606            selectChildView(vh, ibh == null ? null : ibh.itemView, false);
607        } else {
608            if (mHoverCardPresenterSelector != null) {
609                vh.mHoverCardViewSwitcher.unselect();
610            }
611        }
612    }
613
614    private void setupFadingEffect(ListRowView rowView) {
615        // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding.
616        HorizontalGridView gridView = rowView.getGridView();
617        if (mBrowseRowsFadingEdgeLength < 0) {
618            TypedArray ta = gridView.getContext()
619                    .obtainStyledAttributes(R.styleable.LeanbackTheme);
620            mBrowseRowsFadingEdgeLength = (int) ta.getDimension(
621                    R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0);
622            ta.recycle();
623        }
624        gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength);
625    }
626
627    @Override
628    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
629        super.onRowViewExpanded(holder, expanded);
630        ViewHolder vh = (ViewHolder) holder;
631        if (getRowHeight() != getExpandedRowHeight()) {
632            int newHeight = expanded ? getExpandedRowHeight() : getRowHeight();
633            vh.getGridView().setRowHeight(newHeight);
634        }
635        setVerticalPadding(vh);
636        updateFooterViewSwitcher(vh);
637    }
638
639    @Override
640    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
641        super.onBindRowViewHolder(holder, item);
642        ViewHolder vh = (ViewHolder) holder;
643        ListRow rowItem = (ListRow) item;
644        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
645        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
646        vh.mGridView.setContentDescription(rowItem.getContentDescription());
647    }
648
649    @Override
650    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
651        ViewHolder vh = (ViewHolder) holder;
652        vh.mGridView.setAdapter(null);
653        vh.mItemBridgeAdapter.clear();
654        super.onUnbindRowViewHolder(holder);
655    }
656
657    /**
658     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
659     * and return false.
660     */
661    @Override
662    public final boolean isUsingDefaultSelectEffect() {
663        return false;
664    }
665
666    /**
667     * Returns true so that default select effect is applied to each individual
668     * child of {@link HorizontalGridView}.  Subclass may return false to disable
669     * the default implementation.
670     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
671     */
672    public boolean isUsingDefaultListSelectEffect() {
673        return true;
674    }
675
676    /**
677     * Default implementation returns true if SDK version >= 21, shadow (either static or z-order
678     * based) will be applied to each individual child of {@link HorizontalGridView}.
679     * Subclass may return false to disable default implementation of shadow and provide its own.
680     */
681    public boolean isUsingDefaultShadow() {
682        return ShadowOverlayHelper.supportsShadow();
683    }
684
685    /**
686     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
687     * on each child of horizontal list.   If subclass returns false in isUsingDefaultShadow()
688     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
689     */
690    public boolean isUsingZOrder(Context context) {
691        return !Settings.getInstance(context).preferStaticShadows();
692    }
693
694    /**
695     * Enables or disables child shadow.
696     * This is not only for enable/disable default shadow implementation but also subclass must
697     * respect this flag.
698     */
699    public final void setShadowEnabled(boolean enabled) {
700        mShadowEnabled = enabled;
701    }
702
703    /**
704     * Returns true if child shadow is enabled.
705     * This is not only for enable/disable default shadow implementation but also subclass must
706     * respect this flag.
707     */
708    public final boolean getShadowEnabled() {
709        return mShadowEnabled;
710    }
711
712    /**
713     * Enables or disabled rounded corners on children of this row.
714     * Supported on Android SDK >= L.
715     */
716    public final void enableChildRoundedCorners(boolean enable) {
717        mRoundedCornersEnabled = enable;
718    }
719
720    /**
721     * Returns true if rounded corners are enabled for children of this row.
722     */
723    public final boolean areChildRoundedCornersEnabled() {
724        return mRoundedCornersEnabled;
725    }
726
727    final boolean needsDefaultShadow() {
728        return isUsingDefaultShadow() && getShadowEnabled();
729    }
730
731    /**
732     * When ListRowPresenter applies overlay color on the child,  it may change child's foreground
733     * Drawable.  If application uses child's foreground for other purposes such as ripple effect,
734     * it needs tell ListRowPresenter to keep the child's foreground.  The default value is true.
735     *
736     * @param keep true if keep foreground of child of this row, false ListRowPresenter might change
737     *             the foreground of the child.
738     */
739    public final void setKeepChildForeground(boolean keep) {
740        mKeepChildForeground = keep;
741    }
742
743    /**
744     * Returns true if keeps foreground of child of this row, false otherwise.  When
745     * ListRowPresenter applies overlay color on the child,  it may change child's foreground
746     * Drawable.  If application uses child's foreground for other purposes such as ripple effect,
747     * it needs tell ListRowPresenter to keep the child's foreground.  The default value is true.
748     *
749     * @return true if keeps foreground of child of this row, false otherwise.
750     */
751    public final boolean isKeepChildForeground() {
752        return mKeepChildForeground;
753    }
754
755    /**
756     * Create ShadowOverlayHelper Options.  Subclass may override.
757     * e.g.
758     * <code>
759     * return new ShadowOverlayHelper.Options().roundedCornerRadius(10);
760     * </code>
761     *
762     * @return The options to be used for shadow, overlay and rounded corner.
763     */
764    protected ShadowOverlayHelper.Options createShadowOverlayOptions() {
765        return ShadowOverlayHelper.Options.DEFAULT;
766    }
767
768    /**
769     * Applies select level to header and draw a default color dim over each child
770     * of {@link HorizontalGridView}.
771     * <p>
772     * Subclass may override this method.  A subclass
773     * needs to call super.onSelectLevelChanged() for applying header select level
774     * and optionally applying a default select level to each child view of
775     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
776     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
777     * false and deal with the individual item select level by itself.
778     * </p>
779     */
780    @Override
781    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
782        super.onSelectLevelChanged(holder);
783        if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
784            ViewHolder vh = (ViewHolder) holder;
785            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
786            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
787                mShadowOverlayHelper.setOverlayColor(vh.mGridView.getChildAt(i), dimmedColor);
788            }
789            if (vh.mGridView.getFadingLeftEdge()) {
790                vh.mGridView.invalidate();
791            }
792        }
793    }
794
795    @Override
796    public void freeze(RowPresenter.ViewHolder holder, boolean freeze) {
797        ViewHolder vh = (ViewHolder) holder;
798        vh.mGridView.setScrollEnabled(!freeze);
799    }
800
801    @Override
802    public void setEntranceTransitionState(RowPresenter.ViewHolder holder,
803            boolean afterEntrance) {
804        super.setEntranceTransitionState(holder, afterEntrance);
805        ((ViewHolder) holder).mGridView.setChildrenVisibility(
806                afterEntrance? View.VISIBLE : View.INVISIBLE);
807    }
808}
809