DetailsFragment.java revision 6525e063fbbd691a8553f4fc77f3960f93bea34d
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.app;
15
16import android.app.Activity;
17import android.app.Fragment;
18import android.app.FragmentTransaction;
19import android.graphics.Rect;
20import android.graphics.drawable.Drawable;
21import android.os.Build;
22import android.os.Bundle;
23import android.support.annotation.CallSuper;
24import android.support.v17.leanback.R;
25import android.support.v17.leanback.transition.TransitionHelper;
26import android.support.v17.leanback.transition.TransitionListener;
27import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
28import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
29import android.support.v17.leanback.widget.BrowseFrameLayout;
30import android.support.v17.leanback.widget.DetailsParallax;
31import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
32import android.support.v17.leanback.widget.ItemAlignmentFacet;
33import android.support.v17.leanback.widget.ItemBridgeAdapter;
34import android.support.v17.leanback.widget.ObjectAdapter;
35import android.support.v17.leanback.widget.Presenter;
36import android.support.v17.leanback.widget.PresenterSelector;
37import android.support.v17.leanback.widget.RowPresenter;
38import android.support.v17.leanback.widget.VerticalGridView;
39import android.util.Log;
40import android.view.KeyEvent;
41import android.view.LayoutInflater;
42import android.view.View;
43import android.view.ViewGroup;
44
45import java.lang.ref.WeakReference;
46
47/**
48 * A fragment for creating Leanback details screens.
49 *
50 * <p>
51 * A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set
52 * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
53 * of {@link RowPresenter}.
54 * </p>
55 *
56 * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsFragment will
57 * setup default behavior of the DetailsOverviewRow:
58 * <li>
59 * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
60 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
61 * </li>
62 * <li>
63 * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
64 * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
65 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
66 * </li>
67 *
68 * <p>
69 * The recommended activity themes to use with a DetailsFragment are
70 * <li>
71 * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity
72 * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
73 * </li>
74 * <li>
75 * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
76 * if shared element transition is not needed, for example if first row is not rendered by
77 * {@link FullWidthDetailsOverviewRowPresenter}.
78 * </li>
79 * </p>
80 */
81public class DetailsFragment extends BaseFragment {
82    static final String TAG = "DetailsFragment";
83    static boolean DEBUG = false;
84
85    /**
86     * Flag for "possibly" having enter transition not finished yet.
87     * @see #mStartAndTransitionFlag
88     */
89    static final int PF_ENTER_TRANSITION_PENDING = 0x1 << 0;
90    /**
91     * Flag for having entrance transition not finished yet.
92     * @see #mStartAndTransitionFlag
93     */
94    static final int PF_ENTRANCE_TRANSITION_PENDING = 0x1 << 1;
95    /**
96     * Flag that onStart() has been called and about to call onSafeStart() when
97     * pending transitions are finished.
98     * @see #mStartAndTransitionFlag
99     */
100    static final int PF_PENDING_START = 0x1 << 2;
101
102    private class SetSelectionRunnable implements Runnable {
103        int mPosition;
104        boolean mSmooth = true;
105
106        SetSelectionRunnable() {
107        }
108
109        @Override
110        public void run() {
111            if (mRowsFragment == null) {
112                return;
113            }
114            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
115        }
116    }
117
118    /**
119     * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
120     * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
121     * @see #mStartAndTransitionFlag
122     */
123    static class WaitEnterTransitionTimeout implements Runnable {
124        static final long WAIT_ENTERTRANSITION_START = 200;
125
126        final WeakReference<DetailsFragment> mRef;
127
128        WaitEnterTransitionTimeout(DetailsFragment f) {
129            mRef = new WeakReference(f);
130            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
131        }
132
133        @Override
134        public void run() {
135            DetailsFragment f = mRef.get();
136            if (f != null) {
137                f.clearPendingEnterTransition();
138            }
139        }
140    }
141
142    /**
143     * @see #mStartAndTransitionFlag
144     */
145    TransitionListener mEnterTransitionListener = new TransitionListener() {
146        @Override
147        public void onTransitionStart(Object transition) {
148            if (mWaitEnterTransitionTimeout != null) {
149                // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
150                // when transition finishes.
151                mWaitEnterTransitionTimeout.mRef.clear();
152            }
153        }
154
155        @Override
156        public void onTransitionCancel(Object transition) {
157            clearPendingEnterTransition();
158        }
159
160        @Override
161        public void onTransitionEnd(Object transition) {
162            clearPendingEnterTransition();
163        }
164    };
165
166    TransitionListener mReturnTransitionListener = new TransitionListener() {
167        @Override
168        public void onTransitionStart(Object transition) {
169            onReturnTransitionStart();
170        }
171    };
172
173    BrowseFrameLayout mRootView;
174    View mBackgroundView;
175    Drawable mBackgroundDrawable;
176    Fragment mVideoFragment;
177    DetailsParallax mDetailsParallax;
178    RowsFragment mRowsFragment;
179    ObjectAdapter mAdapter;
180    int mContainerListAlignTop;
181    BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
182    BaseOnItemViewClickedListener mOnItemViewClickedListener;
183    DetailsFragmentBackgroundController mDetailsBackgroundController;
184
185
186    /**
187     * Flags for enter transition, entrance transition and onStart.  When onStart() is called
188     * and both enter transiton and entrance transition are finished, we could call onSafeStart().
189     * 1. in onCreate:
190     *      if user call prepareEntranceTransition, set PF_ENTRANCE_TRANSITION_PENDING
191     *      if there is enterTransition, set PF_ENTER_TRANSITION_PENDING, but we dont know if
192     *      user will run enterTransition or not.
193     * 2. when user add row, start WaitEnterTransitionTimeout to wait possible enter transition
194     * start. If enter transition onTransitionStart is not invoked with a period, we can assume
195     * there is no enter transition running, then WaitEnterTransitionTimeout will clear
196     * PF_ENTER_TRANSITION_PENDING.
197     * 3. When enterTransition runs (either postponed or not),  we will stop the
198     * WaitEnterTransitionTimeout, and let onTransitionEnd/onTransitionCancel to clear
199     * PF_ENTER_TRANSITION_PENDING.
200     */
201    int mStartAndTransitionFlag = 0;
202    WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
203
204    Object mSceneAfterEntranceTransition;
205
206    final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
207
208    final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
209            new BaseOnItemViewSelectedListener<Object>() {
210        @Override
211        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
212                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
213            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
214            int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition();
215            if (DEBUG) Log.v(TAG, "row selected position " + position
216                    + " subposition " + subposition);
217            onRowSelected(position, subposition);
218            if (mExternalOnItemViewSelectedListener != null) {
219                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
220                        rowViewHolder, row);
221            }
222        }
223    };
224
225    /**
226     * Sets the list of rows for the fragment.
227     */
228    public void setAdapter(ObjectAdapter adapter) {
229        mAdapter = adapter;
230        Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
231        if (presenters != null) {
232            for (int i = 0; i < presenters.length; i++) {
233                setupPresenter(presenters[i]);
234            }
235        } else {
236            Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
237        }
238        if (mRowsFragment != null) {
239            mRowsFragment.setAdapter(adapter);
240        }
241    }
242
243    /**
244     * Returns the list of rows.
245     */
246    public ObjectAdapter getAdapter() {
247        return mAdapter;
248    }
249
250    /**
251     * Sets an item selection listener.
252     */
253    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
254        mExternalOnItemViewSelectedListener = listener;
255    }
256
257    /**
258     * Sets an item clicked listener.
259     */
260    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
261        if (mOnItemViewClickedListener != listener) {
262            mOnItemViewClickedListener = listener;
263            if (mRowsFragment != null) {
264                mRowsFragment.setOnItemViewClickedListener(listener);
265            }
266        }
267    }
268
269    /**
270     * Returns the item clicked listener.
271     */
272    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
273        return mOnItemViewClickedListener;
274    }
275
276    @Override
277    public void onCreate(Bundle savedInstanceState) {
278        super.onCreate(savedInstanceState);
279        mContainerListAlignTop =
280            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
281
282        Activity activity = getActivity();
283        if (activity != null) {
284            Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
285            if (transition != null) {
286                mStartAndTransitionFlag |= PF_ENTER_TRANSITION_PENDING;
287                TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
288            }
289            transition = TransitionHelper.getReturnTransition(activity.getWindow());
290            if (transition != null) {
291                TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
292            }
293        }
294    }
295
296    @Override
297    public View onCreateView(LayoutInflater inflater, ViewGroup container,
298            Bundle savedInstanceState) {
299        mRootView = (BrowseFrameLayout) inflater.inflate(
300                R.layout.lb_details_fragment, container, false);
301        mBackgroundView = mRootView.findViewById(R.id.details_background_view);
302        if (mBackgroundView != null) {
303            mBackgroundView.setBackground(mBackgroundDrawable);
304        }
305        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
306                R.id.details_rows_dock);
307        if (mRowsFragment == null) {
308            mRowsFragment = new RowsFragment();
309            getChildFragmentManager().beginTransaction()
310                    .replace(R.id.details_rows_dock, mRowsFragment).commit();
311        }
312        installTitleView(inflater, mRootView, savedInstanceState);
313        mRowsFragment.setAdapter(mAdapter);
314        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
315        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
316
317        mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
318            @Override
319            public void run() {
320                mRowsFragment.setEntranceTransitionState(true);
321            }
322        });
323
324        setupDpadNavigation();
325
326        if (Build.VERSION.SDK_INT >= 21) {
327            // Setup adapter listener to work with ParallaxTransition (>= API 21).
328            mRowsFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
329                @Override
330                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
331                    if (mDetailsParallax != null && vh.getViewHolder()
332                            instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
333                        FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
334                                (FullWidthDetailsOverviewRowPresenter.ViewHolder)
335                                        vh.getViewHolder();
336                        rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
337                                mDetailsParallax);
338                    }
339                }
340            });
341        }
342        return mRootView;
343    }
344
345    /**
346     * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
347     */
348    @Deprecated
349    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
350            Bundle savedInstanceState) {
351        return super.onInflateTitleView(inflater, parent, savedInstanceState);
352    }
353
354    @Override
355    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
356                                   Bundle savedInstanceState) {
357        return inflateTitle(inflater, parent, savedInstanceState);
358    }
359
360    void setVerticalGridViewLayout(VerticalGridView listview) {
361        // align the top edge of item to a fixed position
362        listview.setItemAlignmentOffset(-mContainerListAlignTop);
363        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
364        listview.setWindowAlignmentOffset(0);
365        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
366        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
367    }
368
369    /**
370     * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
371     * that setup should only change the Presenter behavior that is meaningful in DetailsFragment.
372     * For example how a row is aligned in details Fragment.   The default implementation invokes
373     * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
374     *
375     */
376    protected void setupPresenter(Presenter rowPresenter) {
377        if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
378            setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
379        }
380    }
381
382    /**
383     * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
384     * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
385     * FullWidthDetailsOverviewRowPresenter to align in fragment.
386     */
387    protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
388        ItemAlignmentFacet facet = new ItemAlignmentFacet();
389        // by default align details_frame to half window height
390        ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
391        alignDef1.setItemAlignmentViewId(R.id.details_frame);
392        alignDef1.setItemAlignmentOffset(- getResources()
393                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
394        alignDef1.setItemAlignmentOffsetPercent(0);
395        // when description is selected, align details_frame to top edge
396        ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
397        alignDef2.setItemAlignmentViewId(R.id.details_frame);
398        alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
399        alignDef2.setItemAlignmentOffset(- getResources()
400                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
401        alignDef2.setItemAlignmentOffsetPercent(0);
402        ItemAlignmentFacet.ItemAlignmentDef[] defs =
403                new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
404        facet.setAlignmentDefs(defs);
405        presenter.setFacet(ItemAlignmentFacet.class, facet);
406    }
407
408    VerticalGridView getVerticalGridView() {
409        return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
410    }
411
412    /**
413     * Gets embedded RowsFragment showing multiple rows for DetailsFragment.  If view of
414     * DetailsFragment is not created, the method returns null.
415     * @return Embedded RowsFragment showing multiple rows for DetailsFragment.
416     */
417    public RowsFragment getRowsFragment() {
418        return mRowsFragment;
419    }
420
421    /**
422     * Setup dimensions that are only meaningful when the child Fragments are inside
423     * DetailsFragment.
424     */
425    private void setupChildFragmentLayout() {
426        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
427    }
428
429    /**
430     * Sets the selected row position with smooth animation.
431     */
432    public void setSelectedPosition(int position) {
433        setSelectedPosition(position, true);
434    }
435
436    /**
437     * Sets the selected row position.
438     */
439    public void setSelectedPosition(int position, boolean smooth) {
440        mSetSelectionRunnable.mPosition = position;
441        mSetSelectionRunnable.mSmooth = smooth;
442        if (getView() != null && getView().getHandler() != null) {
443            getView().getHandler().post(mSetSelectionRunnable);
444        }
445    }
446
447    /**
448     * This method asks DetailsFragmentBackgroundController to add a fragment for rendering video.
449     * In case the fragment is already there, it will return the existing one. The method must be
450     * called after calling super.onCreate(). App usually does not call this method directly.
451     *
452     * @return Fragment the added or restored fragment responsible for rendering video.
453     * @see DetailsFragmentBackgroundController#onCreateVideoFragment()
454     */
455    final Fragment findOrCreateVideoFragment() {
456        Fragment fragment = getChildFragmentManager()
457                .findFragmentById(R.id.video_surface_container);
458        if (fragment == null && mDetailsBackgroundController != null) {
459            FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
460            ft2.add(android.support.v17.leanback.R.id.video_surface_container,
461                    fragment = mDetailsBackgroundController.onCreateVideoFragment());
462            ft2.commit();
463        }
464        mVideoFragment = fragment;
465        return mVideoFragment;
466    }
467
468    void onRowSelected(int selectedPosition, int selectedSubPosition) {
469        ObjectAdapter adapter = getAdapter();
470        if (( mRowsFragment != null && mRowsFragment.getView() != null
471                && mRowsFragment.getView().hasFocus())
472                && (adapter == null || adapter.size() == 0
473                || (getVerticalGridView().getSelectedPosition() == 0
474                && getVerticalGridView().getSelectedSubPosition() == 0))) {
475            showTitle(true);
476        } else {
477            showTitle(false);
478        }
479        if (adapter != null && adapter.size() > selectedPosition) {
480            final VerticalGridView gridView = getVerticalGridView();
481            final int count = gridView.getChildCount();
482            if (count > 0 && (mStartAndTransitionFlag & PF_ENTER_TRANSITION_PENDING) != 0) {
483                if (mWaitEnterTransitionTimeout == null) {
484                    mWaitEnterTransitionTimeout = new WaitEnterTransitionTimeout(this);
485                }
486            }
487            for (int i = 0; i < count; i++) {
488                ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
489                        gridView.getChildViewHolder(gridView.getChildAt(i));
490                RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
491                onSetRowStatus(rowPresenter,
492                        rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
493                        bridgeViewHolder.getAdapterPosition(),
494                        selectedPosition, selectedSubPosition);
495            }
496        }
497    }
498
499    void clearPendingEnterTransition() {
500        if ((mStartAndTransitionFlag & PF_ENTER_TRANSITION_PENDING) != 0) {
501            mStartAndTransitionFlag &= ~PF_ENTER_TRANSITION_PENDING;
502            dispatchOnStartAndTransitionFinished();
503        }
504    }
505
506    void dispatchOnStartAndTransitionFinished() {
507        /**
508         * if onStart() was called and there is no pending enter transition or entrance transition.
509         */
510        if ((mStartAndTransitionFlag & PF_PENDING_START) != 0
511                && (mStartAndTransitionFlag
512                & (PF_ENTER_TRANSITION_PENDING | PF_ENTRANCE_TRANSITION_PENDING)) == 0) {
513            mStartAndTransitionFlag &= ~PF_PENDING_START;
514            onSafeStart();
515        }
516    }
517
518    /**
519     * Called when onStart and enter transition (postponed/none postponed) and entrance transition
520     * are all finished.
521     */
522    @CallSuper
523    void onSafeStart() {
524        if (mDetailsBackgroundController != null) {
525            mDetailsBackgroundController.onStart();
526        }
527    }
528
529    @CallSuper
530    void onReturnTransitionStart() {
531        if (mDetailsBackgroundController != null) {
532            // first disable parallax effect that auto-start PlaybackGlue.
533            boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
534            // if video is not visible we can safely remove VideoFragment,
535            // otherwise let video playing during return transition.
536            if (!isVideoVisible && mVideoFragment != null) {
537                FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
538                ft2.remove(mVideoFragment);
539                ft2.commit();
540                mVideoFragment = null;
541            }
542        }
543    }
544
545    @Override
546    public void onStop() {
547        if (mDetailsBackgroundController != null) {
548            mDetailsBackgroundController.onStop();
549        }
550        super.onStop();
551    }
552
553    /**
554     * Called on every visible row to change view status when current selected row position
555     * or selected sub position changed.  Subclass may override.   The default
556     * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
557     * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
558     * instance of {@link FullWidthDetailsOverviewRowPresenter}.
559     *
560     * @param presenter   The presenter used to create row ViewHolder.
561     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
562     *                    be selected.
563     * @param adapterPosition  The adapter position of viewHolder inside adapter.
564     * @param selectedPosition The adapter position of currently selected row.
565     * @param selectedSubPosition The sub position within currently selected row.  This is used
566     *                            When a row has multiple alignment positions.
567     */
568    protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
569            adapterPosition, int selectedPosition, int selectedSubPosition) {
570        if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
571            onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
572                    (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
573                    adapterPosition, selectedPosition, selectedSubPosition);
574        }
575    }
576
577    /**
578     * Called to change DetailsOverviewRow view status when current selected row position
579     * or selected sub position changed.  Subclass may override.   The default
580     * implementation switches between three states based on the positions:
581     * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
582     * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
583     * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
584     *
585     * @param presenter   The presenter used to create row ViewHolder.
586     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
587     *                    be selected.
588     * @param adapterPosition  The adapter position of viewHolder inside adapter.
589     * @param selectedPosition The adapter position of currently selected row.
590     * @param selectedSubPosition The sub position within currently selected row.  This is used
591     *                            When a row has multiple alignment positions.
592     */
593    protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
594            FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
595            int selectedPosition, int selectedSubPosition) {
596        if (selectedPosition > adapterPosition) {
597            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
598        } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
599            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
600        } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
601            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
602        } else {
603            presenter.setState(viewHolder,
604                    FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
605        }
606    }
607
608    @Override
609    public void onStart() {
610        super.onStart();
611
612        mStartAndTransitionFlag |= PF_PENDING_START;
613        dispatchOnStartAndTransitionFinished();
614
615        setupChildFragmentLayout();
616        if (isEntranceTransitionEnabled()) {
617            mRowsFragment.setEntranceTransitionState(false);
618        }
619        if (mDetailsParallax != null) {
620            mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
621        }
622        if (!getView().hasFocus()) {
623            mRowsFragment.getVerticalGridView().requestFocus();
624        }
625    }
626
627    @Override
628    protected Object createEntranceTransition() {
629        return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
630                R.transition.lb_details_enter_transition);
631    }
632
633    @Override
634    protected void runEntranceTransition(Object entranceTransition) {
635        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
636    }
637
638    @Override
639    protected void onEntranceTransitionEnd() {
640        mStartAndTransitionFlag &= ~PF_ENTRANCE_TRANSITION_PENDING;
641        dispatchOnStartAndTransitionFinished();
642        mRowsFragment.onTransitionEnd();
643    }
644
645    @Override
646    protected void onEntranceTransitionPrepare() {
647        mStartAndTransitionFlag |= PF_ENTRANCE_TRANSITION_PENDING;
648        mRowsFragment.onTransitionPrepare();
649    }
650
651    @Override
652    protected void onEntranceTransitionStart() {
653        mRowsFragment.onTransitionStart();
654    }
655
656    /**
657     * Returns the {@link DetailsParallax} instance used by
658     * {@link DetailsFragmentBackgroundController} to configure parallax effect of background and
659     * control embedded video playback. App usually does not use this method directly.
660     * App may use this method for other custom parallax tasks.
661     *
662     * @return The DetailsParallax instance attached to the DetailsFragment.
663     */
664    public DetailsParallax getParallax() {
665        if (mDetailsParallax == null) {
666            mDetailsParallax = new DetailsParallax();
667            if (mRowsFragment != null && mRowsFragment.getView() != null) {
668                mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
669            }
670        }
671        return mDetailsParallax;
672    }
673
674    /**
675     * Set background drawable shown below foreground rows UI and above
676     * {@link #findOrCreateVideoFragment()}.
677     *
678     * @see DetailsFragmentBackgroundController
679     */
680    void setBackgroundDrawable(Drawable drawable) {
681        if (mBackgroundView != null) {
682            mBackgroundView.setBackground(drawable);
683        }
684        mBackgroundDrawable = drawable;
685    }
686
687    /**
688     * This method does the following
689     * <ul>
690     * <li>sets up focus search handling logic in the root view to enable transitioning between
691     * half screen/full screen/no video mode.</li>
692     *
693     * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
694     * transition to appropriate mode like half/full screen video.</li>
695     * </ul>
696     */
697    void setupDpadNavigation() {
698        mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
699
700            @Override
701            public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
702                return false;
703            }
704
705            @Override
706            public void onRequestChildFocus(View child, View focused) {
707                if (child != mRootView.getFocusedChild()) {
708                    if (child.getId() == R.id.details_fragment_root) {
709                        slideInGridView();
710                        showTitle(true);
711                    } else if (child.getId() == R.id.video_surface_container) {
712                        slideOutGridView();
713                        showTitle(false);
714                    } else {
715                        showTitle(true);
716                    }
717                }
718            }
719        });
720        mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
721            @Override
722            public View onFocusSearch(View focused, int direction) {
723                if (mRowsFragment.getVerticalGridView() != null
724                        && mRowsFragment.getVerticalGridView().hasFocus()) {
725                    if (direction == View.FOCUS_UP) {
726                        if (mVideoFragment != null && mVideoFragment.getView() != null) {
727                            return mVideoFragment.getView();
728                        } else if (getTitleView() != null && getTitleView().hasFocusable()) {
729                            return getTitleView();
730                        }
731                    }
732                } else if (getTitleView() != null && getTitleView().hasFocus()) {
733                    if (direction == View.FOCUS_DOWN) {
734                        if (mRowsFragment.getVerticalGridView() != null) {
735                            return mRowsFragment.getVerticalGridView();
736                        }
737                    }
738                }
739                return focused;
740            }
741        });
742
743        // If we press BACK on remote while in full screen video mode, we should
744        // transition back to half screen video playback mode.
745        mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
746            @Override
747            public boolean onKey(View v, int keyCode, KeyEvent event) {
748                // This is used to check if we are in full screen video mode. This is somewhat
749                // hacky and relies on the behavior of the video helper class to update the
750                // focusability of the video surface view.
751                if (mVideoFragment != null && mVideoFragment.getView() != null
752                        && mVideoFragment.getView().hasFocus()) {
753                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
754                        getVerticalGridView().requestFocus();
755                        return true;
756                    }
757                }
758
759                return false;
760            }
761        });
762    }
763
764    /**
765     * Slides vertical grid view (displaying media item details) out of the screen from below.
766     */
767    void slideOutGridView() {
768        if (getVerticalGridView() != null) {
769            getVerticalGridView().animateOut();
770        }
771    }
772
773    void slideInGridView() {
774        if (getVerticalGridView() != null) {
775            getVerticalGridView().animateIn();
776        }
777    }
778}
779