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