Home | History | Annotate | Download | only in app
      1 // CHECKSTYLE:OFF Generated code
      2 /* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */
      3 
      4 /*
      5  * Copyright (C) 2014 The Android Open Source Project
      6  *
      7  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      8  * in compliance with the License. You may obtain a copy of the License at
      9  *
     10  * http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software distributed under the License
     13  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     14  * or implied. See the License for the specific language governing permissions and limitations under
     15  * the License.
     16  */
     17 package androidx.leanback.app;
     18 
     19 import android.graphics.Rect;
     20 import android.graphics.drawable.Drawable;
     21 import android.os.Build;
     22 import android.os.Bundle;
     23 import android.util.Log;
     24 import android.view.KeyEvent;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.view.Window;
     29 
     30 import androidx.annotation.CallSuper;
     31 import androidx.fragment.app.Fragment;
     32 import androidx.fragment.app.FragmentActivity;
     33 import androidx.fragment.app.FragmentTransaction;
     34 import androidx.leanback.R;
     35 import androidx.leanback.transition.TransitionHelper;
     36 import androidx.leanback.transition.TransitionListener;
     37 import androidx.leanback.util.StateMachine.Event;
     38 import androidx.leanback.util.StateMachine.State;
     39 import androidx.leanback.widget.BaseOnItemViewClickedListener;
     40 import androidx.leanback.widget.BaseOnItemViewSelectedListener;
     41 import androidx.leanback.widget.BrowseFrameLayout;
     42 import androidx.leanback.widget.DetailsParallax;
     43 import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
     44 import androidx.leanback.widget.ItemAlignmentFacet;
     45 import androidx.leanback.widget.ItemBridgeAdapter;
     46 import androidx.leanback.widget.ObjectAdapter;
     47 import androidx.leanback.widget.Presenter;
     48 import androidx.leanback.widget.PresenterSelector;
     49 import androidx.leanback.widget.RowPresenter;
     50 import androidx.leanback.widget.VerticalGridView;
     51 
     52 import java.lang.ref.WeakReference;
     53 
     54 /**
     55  * A fragment for creating Leanback details screens.
     56  *
     57  * <p>
     58  * A DetailsSupportFragment renders the elements of its {@link ObjectAdapter} as a set
     59  * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
     60  * of {@link RowPresenter}.
     61  * </p>
     62  *
     63  * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsSupportFragment will
     64  * setup default behavior of the DetailsOverviewRow:
     65  * <li>
     66  * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
     67  * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
     68  * </li>
     69  * <li>
     70  * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
     71  * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
     72  * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
     73  * </li>
     74  *
     75  * <p>
     76  * The recommended activity themes to use with a DetailsSupportFragment are
     77  * <li>
     78  * {@link androidx.leanback.R.style#Theme_Leanback_Details} with activity
     79  * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
     80  * </li>
     81  * <li>
     82  * {@link androidx.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
     83  * if shared element transition is not needed, for example if first row is not rendered by
     84  * {@link FullWidthDetailsOverviewRowPresenter}.
     85  * </li>
     86  * </p>
     87  *
     88  * <p>
     89  * DetailsSupportFragment can use {@link DetailsSupportFragmentBackgroundController} to add a parallax drawable
     90  * background and embedded video playing fragment.
     91  * </p>
     92  */
     93 public class DetailsSupportFragment extends BaseSupportFragment {
     94     static final String TAG = "DetailsSupportFragment";
     95     static final boolean DEBUG = false;
     96 
     97     final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
     98         @Override
     99         public void run() {
    100             mRowsSupportFragment.setEntranceTransitionState(false);
    101         }
    102     };
    103 
    104     final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
    105 
    106     void switchToVideoBeforeVideoSupportFragmentCreated() {
    107         // if the video fragment is not ready: immediately fade out covering drawable,
    108         // hide title and mark mPendingFocusOnVideo and set focus on it later.
    109         mDetailsBackgroundController.switchToVideoBeforeCreate();
    110         showTitle(false);
    111         mPendingFocusOnVideo = true;
    112         slideOutGridView();
    113     }
    114 
    115     final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
    116             false, false) {
    117         @Override
    118         public void run() {
    119             switchToVideoBeforeVideoSupportFragmentCreated();
    120         }
    121     };
    122 
    123     final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
    124             false, false) {
    125         @Override
    126         public void run() {
    127             if (mWaitEnterTransitionTimeout != null) {
    128                 mWaitEnterTransitionTimeout.mRef.clear();
    129             }
    130             // clear the activity enter/sharedElement transition, return transitions are kept.
    131             // keep the return transitions and clear enter transition
    132             if (getActivity() != null) {
    133                 Window window = getActivity().getWindow();
    134                 Object returnTransition = TransitionHelper.getReturnTransition(window);
    135                 Object sharedReturnTransition = TransitionHelper
    136                         .getSharedElementReturnTransition(window);
    137                 TransitionHelper.setEnterTransition(window, null);
    138                 TransitionHelper.setSharedElementEnterTransition(window, null);
    139                 TransitionHelper.setReturnTransition(window, returnTransition);
    140                 TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
    141             }
    142         }
    143     };
    144 
    145     final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
    146             true, false);
    147 
    148     final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
    149         @Override
    150         public void run() {
    151             Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
    152             TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
    153         }
    154     };
    155 
    156     final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
    157         @Override
    158         public void run() {
    159             if (mWaitEnterTransitionTimeout == null) {
    160                 new WaitEnterTransitionTimeout(DetailsSupportFragment.this);
    161             }
    162         }
    163     };
    164 
    165     /**
    166      * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
    167      * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
    168      */
    169     static class WaitEnterTransitionTimeout implements Runnable {
    170         static final long WAIT_ENTERTRANSITION_START = 200;
    171 
    172         final WeakReference<DetailsSupportFragment> mRef;
    173 
    174         WaitEnterTransitionTimeout(DetailsSupportFragment f) {
    175             mRef = new WeakReference<>(f);
    176             f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
    177         }
    178 
    179         @Override
    180         public void run() {
    181             DetailsSupportFragment f = mRef.get();
    182             if (f != null) {
    183                 f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
    184             }
    185         }
    186     }
    187 
    188     final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
    189         @Override
    190         public void run() {
    191             onSafeStart();
    192         }
    193     };
    194 
    195     final Event EVT_ONSTART = new Event("onStart");
    196 
    197     final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
    198 
    199     final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
    200 
    201     final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
    202 
    203     final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
    204 
    205     @Override
    206     void createStateMachineStates() {
    207         super.createStateMachineStates();
    208         mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
    209         mStateMachine.addState(STATE_ON_SAFE_START);
    210         mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
    211         mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
    212         mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
    213         mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
    214         mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
    215         mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
    216     }
    217 
    218     @Override
    219     void createStateMachineTransitions() {
    220         super.createStateMachineTransitions();
    221         /**
    222          * Part 1: Processing enter transitions after fragment.onCreate
    223          */
    224         mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
    225         // if transition is not supported, skip to complete
    226         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
    227                 COND_TRANSITION_NOT_SUPPORTED);
    228         // if transition is not set on Activity, skip to complete
    229         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
    230                 EVT_NO_ENTER_TRANSITION);
    231         // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
    232         // complete.
    233         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
    234                 EVT_SWITCH_TO_VIDEO);
    235         mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
    236         // once after onCreateView, we cannot skip the enter transition, add a listener and wait
    237         // it to finish
    238         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
    239                 EVT_ON_CREATEVIEW);
    240         // when enter transition finishes, go to complete, however this might never happen if
    241         // the activity is not giving transition options in startActivity, there is no API to query
    242         // if this activity is started in a enter transition mode. So we rely on a timer below:
    243         mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
    244                 STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
    245         // we are expecting app to start delayed enter transition shortly after details row is
    246         // loaded, so create a timer and wait for enter transition start.
    247         mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
    248                 STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
    249         // if enter transition not started in the timer, skip to DONE, this can be also true when
    250         // startActivity is not giving transition option.
    251         mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
    252                 EVT_ENTER_TRANSIITON_DONE);
    253 
    254         /**
    255          * Part 2: modification to the entrance transition defined in BaseSupportFragment
    256          */
    257         // Must finish enter transition before perform entrance transition.
    258         mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
    259         // Calling switch to video would hide immediately and skip entrance transition
    260         mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
    261                 EVT_SWITCH_TO_VIDEO);
    262         mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
    263         // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
    264         // still need to do the switchToVideo.
    265         mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
    266                 EVT_SWITCH_TO_VIDEO);
    267 
    268         // for once the view is created in onStart and prepareEntranceTransition was called, we
    269         // could setEntranceStartState:
    270         mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
    271                 STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
    272 
    273         /**
    274          * Part 3: onSafeStart()
    275          */
    276         // for onSafeStart: the condition is onStart called, entrance transition complete
    277         mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
    278         mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
    279         mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
    280     }
    281 
    282     private class SetSelectionRunnable implements Runnable {
    283         int mPosition;
    284         boolean mSmooth = true;
    285 
    286         SetSelectionRunnable() {
    287         }
    288 
    289         @Override
    290         public void run() {
    291             if (mRowsSupportFragment == null) {
    292                 return;
    293             }
    294             mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
    295         }
    296     }
    297 
    298     TransitionListener mEnterTransitionListener = new TransitionListener() {
    299         @Override
    300         public void onTransitionStart(Object transition) {
    301             if (mWaitEnterTransitionTimeout != null) {
    302                 // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
    303                 // when transition finishes.
    304                 mWaitEnterTransitionTimeout.mRef.clear();
    305             }
    306         }
    307 
    308         @Override
    309         public void onTransitionCancel(Object transition) {
    310             mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
    311         }
    312 
    313         @Override
    314         public void onTransitionEnd(Object transition) {
    315             mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
    316         }
    317     };
    318 
    319     TransitionListener mReturnTransitionListener = new TransitionListener() {
    320         @Override
    321         public void onTransitionStart(Object transition) {
    322             onReturnTransitionStart();
    323         }
    324     };
    325 
    326     BrowseFrameLayout mRootView;
    327     View mBackgroundView;
    328     Drawable mBackgroundDrawable;
    329     Fragment mVideoSupportFragment;
    330     DetailsParallax mDetailsParallax;
    331     RowsSupportFragment mRowsSupportFragment;
    332     ObjectAdapter mAdapter;
    333     int mContainerListAlignTop;
    334     BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
    335     BaseOnItemViewClickedListener mOnItemViewClickedListener;
    336     DetailsSupportFragmentBackgroundController mDetailsBackgroundController;
    337 
    338     // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
    339     // true, we will focus to VideoSupportFragment immediately after video fragment's view is created.
    340     boolean mPendingFocusOnVideo = false;
    341 
    342     WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
    343 
    344     Object mSceneAfterEntranceTransition;
    345 
    346     final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
    347 
    348     final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
    349             new BaseOnItemViewSelectedListener<Object>() {
    350         @Override
    351         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
    352                                    RowPresenter.ViewHolder rowViewHolder, Object row) {
    353             int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
    354             int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
    355             if (DEBUG) Log.v(TAG, "row selected position " + position
    356                     + " subposition " + subposition);
    357             onRowSelected(position, subposition);
    358             if (mExternalOnItemViewSelectedListener != null) {
    359                 mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
    360                         rowViewHolder, row);
    361             }
    362         }
    363     };
    364 
    365     /**
    366      * Sets the list of rows for the fragment.
    367      */
    368     public void setAdapter(ObjectAdapter adapter) {
    369         mAdapter = adapter;
    370         Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
    371         if (presenters != null) {
    372             for (int i = 0; i < presenters.length; i++) {
    373                 setupPresenter(presenters[i]);
    374             }
    375         } else {
    376             Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
    377         }
    378         if (mRowsSupportFragment != null) {
    379             mRowsSupportFragment.setAdapter(adapter);
    380         }
    381     }
    382 
    383     /**
    384      * Returns the list of rows.
    385      */
    386     public ObjectAdapter getAdapter() {
    387         return mAdapter;
    388     }
    389 
    390     /**
    391      * Sets an item selection listener.
    392      */
    393     public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
    394         mExternalOnItemViewSelectedListener = listener;
    395     }
    396 
    397     /**
    398      * Sets an item clicked listener.
    399      */
    400     public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
    401         if (mOnItemViewClickedListener != listener) {
    402             mOnItemViewClickedListener = listener;
    403             if (mRowsSupportFragment != null) {
    404                 mRowsSupportFragment.setOnItemViewClickedListener(listener);
    405             }
    406         }
    407     }
    408 
    409     /**
    410      * Returns the item clicked listener.
    411      */
    412     public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
    413         return mOnItemViewClickedListener;
    414     }
    415 
    416     @Override
    417     public void onCreate(Bundle savedInstanceState) {
    418         super.onCreate(savedInstanceState);
    419         mContainerListAlignTop =
    420             getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
    421 
    422         FragmentActivity activity = getActivity();
    423         if (activity != null) {
    424             Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
    425             if (transition == null) {
    426                 mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
    427             }
    428             transition = TransitionHelper.getReturnTransition(activity.getWindow());
    429             if (transition != null) {
    430                 TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
    431             }
    432         } else {
    433             mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
    434         }
    435     }
    436 
    437     @Override
    438     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    439             Bundle savedInstanceState) {
    440         mRootView = (BrowseFrameLayout) inflater.inflate(
    441                 R.layout.lb_details_fragment, container, false);
    442         mBackgroundView = mRootView.findViewById(R.id.details_background_view);
    443         if (mBackgroundView != null) {
    444             mBackgroundView.setBackground(mBackgroundDrawable);
    445         }
    446         mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
    447                 R.id.details_rows_dock);
    448         if (mRowsSupportFragment == null) {
    449             mRowsSupportFragment = new RowsSupportFragment();
    450             getChildFragmentManager().beginTransaction()
    451                     .replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
    452         }
    453         installTitleView(inflater, mRootView, savedInstanceState);
    454         mRowsSupportFragment.setAdapter(mAdapter);
    455         mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
    456         mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
    457 
    458         mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
    459             @Override
    460             public void run() {
    461                 mRowsSupportFragment.setEntranceTransitionState(true);
    462             }
    463         });
    464 
    465         setupDpadNavigation();
    466 
    467         if (Build.VERSION.SDK_INT >= 21) {
    468             // Setup adapter listener to work with ParallaxTransition (>= API 21).
    469             mRowsSupportFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
    470                 @Override
    471                 public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
    472                     if (mDetailsParallax != null && vh.getViewHolder()
    473                             instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
    474                         FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
    475                                 (FullWidthDetailsOverviewRowPresenter.ViewHolder)
    476                                         vh.getViewHolder();
    477                         rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
    478                                 mDetailsParallax);
    479                     }
    480                 }
    481             });
    482         }
    483         return mRootView;
    484     }
    485 
    486     /**
    487      * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
    488      */
    489     @Deprecated
    490     protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
    491             Bundle savedInstanceState) {
    492         return super.onInflateTitleView(inflater, parent, savedInstanceState);
    493     }
    494 
    495     @Override
    496     public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
    497                                    Bundle savedInstanceState) {
    498         return inflateTitle(inflater, parent, savedInstanceState);
    499     }
    500 
    501     void setVerticalGridViewLayout(VerticalGridView listview) {
    502         // align the top edge of item to a fixed position
    503         listview.setItemAlignmentOffset(-mContainerListAlignTop);
    504         listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
    505         listview.setWindowAlignmentOffset(0);
    506         listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
    507         listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    508     }
    509 
    510     /**
    511      * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
    512      * that setup should only change the Presenter behavior that is meaningful in DetailsSupportFragment.
    513      * For example how a row is aligned in details Fragment.   The default implementation invokes
    514      * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
    515      *
    516      */
    517     protected void setupPresenter(Presenter rowPresenter) {
    518         if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
    519             setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
    520         }
    521     }
    522 
    523     /**
    524      * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
    525      * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
    526      * FullWidthDetailsOverviewRowPresenter to align in fragment.
    527      */
    528     protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
    529         ItemAlignmentFacet facet = new ItemAlignmentFacet();
    530         // by default align details_frame to half window height
    531         ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
    532         alignDef1.setItemAlignmentViewId(R.id.details_frame);
    533         alignDef1.setItemAlignmentOffset(- getResources()
    534                 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
    535         alignDef1.setItemAlignmentOffsetPercent(0);
    536         // when description is selected, align details_frame to top edge
    537         ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
    538         alignDef2.setItemAlignmentViewId(R.id.details_frame);
    539         alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
    540         alignDef2.setItemAlignmentOffset(- getResources()
    541                 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
    542         alignDef2.setItemAlignmentOffsetPercent(0);
    543         ItemAlignmentFacet.ItemAlignmentDef[] defs =
    544                 new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
    545         facet.setAlignmentDefs(defs);
    546         presenter.setFacet(ItemAlignmentFacet.class, facet);
    547     }
    548 
    549     VerticalGridView getVerticalGridView() {
    550         return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
    551     }
    552 
    553     /**
    554      * Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.  If view of
    555      * DetailsSupportFragment is not created, the method returns null.
    556      * @return Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.
    557      */
    558     public RowsSupportFragment getRowsSupportFragment() {
    559         return mRowsSupportFragment;
    560     }
    561 
    562     /**
    563      * Setup dimensions that are only meaningful when the child Fragments are inside
    564      * DetailsSupportFragment.
    565      */
    566     private void setupChildFragmentLayout() {
    567         setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
    568     }
    569 
    570     /**
    571      * Sets the selected row position with smooth animation.
    572      */
    573     public void setSelectedPosition(int position) {
    574         setSelectedPosition(position, true);
    575     }
    576 
    577     /**
    578      * Sets the selected row position.
    579      */
    580     public void setSelectedPosition(int position, boolean smooth) {
    581         mSetSelectionRunnable.mPosition = position;
    582         mSetSelectionRunnable.mSmooth = smooth;
    583         if (getView() != null && getView().getHandler() != null) {
    584             getView().getHandler().post(mSetSelectionRunnable);
    585         }
    586     }
    587 
    588     void switchToVideo() {
    589         if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
    590             mVideoSupportFragment.getView().requestFocus();
    591         } else {
    592             mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
    593         }
    594     }
    595 
    596     void switchToRows() {
    597         mPendingFocusOnVideo = false;
    598         VerticalGridView verticalGridView = getVerticalGridView();
    599         if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
    600             verticalGridView.requestFocus();
    601         }
    602     }
    603 
    604     /**
    605      * This method asks DetailsSupportFragmentBackgroundController to add a fragment for rendering video.
    606      * In case the fragment is already there, it will return the existing one. The method must be
    607      * called after calling super.onCreate(). App usually does not call this method directly.
    608      *
    609      * @return Fragment the added or restored fragment responsible for rendering video.
    610      * @see DetailsSupportFragmentBackgroundController#onCreateVideoSupportFragment()
    611      */
    612     final Fragment findOrCreateVideoSupportFragment() {
    613         if (mVideoSupportFragment != null) {
    614             return mVideoSupportFragment;
    615         }
    616         Fragment fragment = getChildFragmentManager()
    617                 .findFragmentById(R.id.video_surface_container);
    618         if (fragment == null && mDetailsBackgroundController != null) {
    619             FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
    620             ft2.add(androidx.leanback.R.id.video_surface_container,
    621                     fragment = mDetailsBackgroundController.onCreateVideoSupportFragment());
    622             ft2.commit();
    623             if (mPendingFocusOnVideo) {
    624                 // wait next cycle for Fragment view created so we can focus on it.
    625                 // This is a bit hack eventually we will do commitNow() which get view immediately.
    626                 getView().post(new Runnable() {
    627                     @Override
    628                     public void run() {
    629                         if (getView() != null) {
    630                             switchToVideo();
    631                         }
    632                         mPendingFocusOnVideo = false;
    633                     }
    634                 });
    635             }
    636         }
    637         mVideoSupportFragment = fragment;
    638         return mVideoSupportFragment;
    639     }
    640 
    641     void onRowSelected(int selectedPosition, int selectedSubPosition) {
    642         ObjectAdapter adapter = getAdapter();
    643         if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
    644                 && mRowsSupportFragment.getView().hasFocus() && !mPendingFocusOnVideo)
    645                 && (adapter == null || adapter.size() == 0
    646                 || (getVerticalGridView().getSelectedPosition() == 0
    647                 && getVerticalGridView().getSelectedSubPosition() == 0))) {
    648             showTitle(true);
    649         } else {
    650             showTitle(false);
    651         }
    652         if (adapter != null && adapter.size() > selectedPosition) {
    653             final VerticalGridView gridView = getVerticalGridView();
    654             final int count = gridView.getChildCount();
    655             if (count > 0) {
    656                 mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
    657             }
    658             for (int i = 0; i < count; i++) {
    659                 ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
    660                         gridView.getChildViewHolder(gridView.getChildAt(i));
    661                 RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
    662                 onSetRowStatus(rowPresenter,
    663                         rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
    664                         bridgeViewHolder.getAdapterPosition(),
    665                         selectedPosition, selectedSubPosition);
    666             }
    667         }
    668     }
    669 
    670     /**
    671      * Called when onStart and enter transition (postponed/none postponed) and entrance transition
    672      * are all finished.
    673      */
    674     @CallSuper
    675     void onSafeStart() {
    676         if (mDetailsBackgroundController != null) {
    677             mDetailsBackgroundController.onStart();
    678         }
    679     }
    680 
    681     @CallSuper
    682     void onReturnTransitionStart() {
    683         if (mDetailsBackgroundController != null) {
    684             // first disable parallax effect that auto-start PlaybackGlue.
    685             boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
    686             // if video is not visible we can safely remove VideoSupportFragment,
    687             // otherwise let video playing during return transition.
    688             if (!isVideoVisible && mVideoSupportFragment != null) {
    689                 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
    690                 ft2.remove(mVideoSupportFragment);
    691                 ft2.commit();
    692                 mVideoSupportFragment = null;
    693             }
    694         }
    695     }
    696 
    697     @Override
    698     public void onStop() {
    699         if (mDetailsBackgroundController != null) {
    700             mDetailsBackgroundController.onStop();
    701         }
    702         super.onStop();
    703     }
    704 
    705     /**
    706      * Called on every visible row to change view status when current selected row position
    707      * or selected sub position changed.  Subclass may override.   The default
    708      * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
    709      * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
    710      * instance of {@link FullWidthDetailsOverviewRowPresenter}.
    711      *
    712      * @param presenter   The presenter used to create row ViewHolder.
    713      * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
    714      *                    be selected.
    715      * @param adapterPosition  The adapter position of viewHolder inside adapter.
    716      * @param selectedPosition The adapter position of currently selected row.
    717      * @param selectedSubPosition The sub position within currently selected row.  This is used
    718      *                            When a row has multiple alignment positions.
    719      */
    720     protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
    721             adapterPosition, int selectedPosition, int selectedSubPosition) {
    722         if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
    723             onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
    724                     (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
    725                     adapterPosition, selectedPosition, selectedSubPosition);
    726         }
    727     }
    728 
    729     /**
    730      * Called to change DetailsOverviewRow view status when current selected row position
    731      * or selected sub position changed.  Subclass may override.   The default
    732      * implementation switches between three states based on the positions:
    733      * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
    734      * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
    735      * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
    736      *
    737      * @param presenter   The presenter used to create row ViewHolder.
    738      * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
    739      *                    be selected.
    740      * @param adapterPosition  The adapter position of viewHolder inside adapter.
    741      * @param selectedPosition The adapter position of currently selected row.
    742      * @param selectedSubPosition The sub position within currently selected row.  This is used
    743      *                            When a row has multiple alignment positions.
    744      */
    745     protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
    746             FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
    747             int selectedPosition, int selectedSubPosition) {
    748         if (selectedPosition > adapterPosition) {
    749             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
    750         } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
    751             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
    752         } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
    753             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
    754         } else {
    755             presenter.setState(viewHolder,
    756                     FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
    757         }
    758     }
    759 
    760     @Override
    761     public void onStart() {
    762         super.onStart();
    763 
    764         setupChildFragmentLayout();
    765         mStateMachine.fireEvent(EVT_ONSTART);
    766         if (mDetailsParallax != null) {
    767             mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
    768         }
    769         if (mPendingFocusOnVideo) {
    770             slideOutGridView();
    771         } else if (!getView().hasFocus()) {
    772             mRowsSupportFragment.getVerticalGridView().requestFocus();
    773         }
    774     }
    775 
    776     @Override
    777     protected Object createEntranceTransition() {
    778         return TransitionHelper.loadTransition(getContext(),
    779                 R.transition.lb_details_enter_transition);
    780     }
    781 
    782     @Override
    783     protected void runEntranceTransition(Object entranceTransition) {
    784         TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
    785     }
    786 
    787     @Override
    788     protected void onEntranceTransitionEnd() {
    789         mRowsSupportFragment.onTransitionEnd();
    790     }
    791 
    792     @Override
    793     protected void onEntranceTransitionPrepare() {
    794         mRowsSupportFragment.onTransitionPrepare();
    795     }
    796 
    797     @Override
    798     protected void onEntranceTransitionStart() {
    799         mRowsSupportFragment.onTransitionStart();
    800     }
    801 
    802     /**
    803      * Returns the {@link DetailsParallax} instance used by
    804      * {@link DetailsSupportFragmentBackgroundController} to configure parallax effect of background and
    805      * control embedded video playback. App usually does not use this method directly.
    806      * App may use this method for other custom parallax tasks.
    807      *
    808      * @return The DetailsParallax instance attached to the DetailsSupportFragment.
    809      */
    810     public DetailsParallax getParallax() {
    811         if (mDetailsParallax == null) {
    812             mDetailsParallax = new DetailsParallax();
    813             if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) {
    814                 mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
    815             }
    816         }
    817         return mDetailsParallax;
    818     }
    819 
    820     /**
    821      * Set background drawable shown below foreground rows UI and above
    822      * {@link #findOrCreateVideoSupportFragment()}.
    823      *
    824      * @see DetailsSupportFragmentBackgroundController
    825      */
    826     void setBackgroundDrawable(Drawable drawable) {
    827         if (mBackgroundView != null) {
    828             mBackgroundView.setBackground(drawable);
    829         }
    830         mBackgroundDrawable = drawable;
    831     }
    832 
    833     /**
    834      * This method does the following
    835      * <ul>
    836      * <li>sets up focus search handling logic in the root view to enable transitioning between
    837      * half screen/full screen/no video mode.</li>
    838      *
    839      * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
    840      * transition to appropriate mode like half/full screen video.</li>
    841      * </ul>
    842      */
    843     void setupDpadNavigation() {
    844         mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
    845 
    846             @Override
    847             public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    848                 return false;
    849             }
    850 
    851             @Override
    852             public void onRequestChildFocus(View child, View focused) {
    853                 if (child != mRootView.getFocusedChild()) {
    854                     if (child.getId() == R.id.details_fragment_root) {
    855                         if (!mPendingFocusOnVideo) {
    856                             slideInGridView();
    857                             showTitle(true);
    858                         }
    859                     } else if (child.getId() == R.id.video_surface_container) {
    860                         slideOutGridView();
    861                         showTitle(false);
    862                     } else {
    863                         showTitle(true);
    864                     }
    865                 }
    866             }
    867         });
    868         mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
    869             @Override
    870             public View onFocusSearch(View focused, int direction) {
    871                 if (mRowsSupportFragment.getVerticalGridView() != null
    872                         && mRowsSupportFragment.getVerticalGridView().hasFocus()) {
    873                     if (direction == View.FOCUS_UP) {
    874                         if (mDetailsBackgroundController != null
    875                                 && mDetailsBackgroundController.canNavigateToVideoSupportFragment()
    876                                 && mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
    877                             return mVideoSupportFragment.getView();
    878                         } else if (getTitleView() != null && getTitleView().hasFocusable()) {
    879                             return getTitleView();
    880                         }
    881                     }
    882                 } else if (getTitleView() != null && getTitleView().hasFocus()) {
    883                     if (direction == View.FOCUS_DOWN) {
    884                         if (mRowsSupportFragment.getVerticalGridView() != null) {
    885                             return mRowsSupportFragment.getVerticalGridView();
    886                         }
    887                     }
    888                 }
    889                 return focused;
    890             }
    891         });
    892 
    893         // If we press BACK on remote while in full screen video mode, we should
    894         // transition back to half screen video playback mode.
    895         mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
    896             @Override
    897             public boolean onKey(View v, int keyCode, KeyEvent event) {
    898                 // This is used to check if we are in full screen video mode. This is somewhat
    899                 // hacky and relies on the behavior of the video helper class to update the
    900                 // focusability of the video surface view.
    901                 if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
    902                         && mVideoSupportFragment.getView().hasFocus()) {
    903                     if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
    904                         if (getVerticalGridView().getChildCount() > 0) {
    905                             getVerticalGridView().requestFocus();
    906                             return true;
    907                         }
    908                     }
    909                 }
    910 
    911                 return false;
    912             }
    913         });
    914     }
    915 
    916     /**
    917      * Slides vertical grid view (displaying media item details) out of the screen from below.
    918      */
    919     void slideOutGridView() {
    920         if (getVerticalGridView() != null) {
    921             getVerticalGridView().animateOut();
    922         }
    923     }
    924 
    925     void slideInGridView() {
    926         if (getVerticalGridView() != null) {
    927             getVerticalGridView().animateIn();
    928         }
    929     }
    930 }
    931