public class

DetailsSupportFragment

extends BaseSupportFragment

Gradle dependencies

compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'

  • groupId: androidx.leanback
  • artifactId: leanback
  • version: 1.2.0-alpha04

Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.leanback:leanback com.android.support:leanback-v17

Androidx class mapping:

androidx.leanback.app.DetailsSupportFragment android.support.v17.leanback.app.DetailsSupportFragment

Overview

A fragment for creating Leanback details screens.

A DetailsSupportFragment renders the elements of its ObjectAdapter as a set of rows in a vertical list.The Adapter's PresenterSelector must maintain subclasses of RowPresenter.

When FullWidthDetailsOverviewRowPresenter is found in adapter, DetailsSupportFragment will setup default behavior of the DetailsOverviewRow:

The recommended activity themes to use with a DetailsSupportFragment are

DetailsSupportFragment can use DetailsSupportFragmentBackgroundController to add a parallax drawable background and embedded video playing fragment.

Summary

Fields
from FragmentmPreviousWho
Constructors
publicDetailsSupportFragment()

Methods
protected java.lang.ObjectcreateEntranceTransition()

Create entrance transition.

public ObjectAdaptergetAdapter()

Returns the list of rows.

public BaseOnItemViewClickedListenergetOnItemViewClickedListener()

Returns the item clicked listener.

public DetailsParallaxgetParallax()

Returns the DetailsParallax instance used by DetailsSupportFragmentBackgroundController to configure parallax effect of background and control embedded video playback.

public RowsSupportFragmentgetRowsSupportFragment()

Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.

protected ViewinflateTitle(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)

public voidonCreate(Bundle savedInstanceState)

Called to do initial creation of a fragment.

public ViewonCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Called to have the fragment instantiate its user interface view.

public voidonDestroyView()

Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has been detached from the fragment.

protected voidonEntranceTransitionEnd()

Callback when entrance transition is ended.

protected voidonEntranceTransitionPrepare()

Callback when entrance transition is prepared.

protected voidonEntranceTransitionStart()

Callback when entrance transition is started.

public ViewonInflateTitleView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)

Called by BrandedSupportFragment.installTitleView(LayoutInflater, ViewGroup, Bundle) to inflate title view.

protected voidonSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter, FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)

Called to change DetailsOverviewRow view status when current selected row position or selected sub position changed.

protected voidonSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)

Called on every visible row to change view status when current selected row position or selected sub position changed.

public voidonStart()

Called when the Fragment is visible to the user.

public voidonStop()

Called when the Fragment is no longer started.

protected voidrunEntranceTransition(java.lang.Object entranceTransition)

Run entrance transition.

public voidsetAdapter(ObjectAdapter adapter)

Sets the list of rows for the fragment.

public voidsetOnItemViewClickedListener(BaseOnItemViewClickedListener listener)

Sets an item clicked listener.

public voidsetOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener)

Sets an item selection listener.

public voidsetSelectedPosition(int position)

Sets the selected row position with smooth animation.

public voidsetSelectedPosition(int position, boolean smooth)

Sets the selected row position.

protected voidsetupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter)

Called to setup FullWidthDetailsOverviewRowPresenter.

protected voidsetupPresenter(Presenter rowPresenter)

Called to setup each Presenter of Adapter passed in DetailsSupportFragment.setAdapter(ObjectAdapter).Note that setup should only change the Presenter behavior that is meaningful in DetailsSupportFragment.

from BaseSupportFragmentgetProgressBarManager, onViewCreated, prepareEntranceTransition, startEntranceTransition
from BrandedSupportFragmentgetBadgeDrawable, getSearchAffordanceColor, getSearchAffordanceColors, getTitle, getTitleView, getTitleViewAdapter, installTitleView, isShowingTitle, onPause, onResume, onSaveInstanceState, setBadgeDrawable, setOnSearchClickedListener, setSearchAffordanceColor, setSearchAffordanceColors, setTitle, setTitleView, showTitle, showTitle
from Fragmentdump, equals, getActivity, getAllowEnterTransitionOverlap, getAllowReturnTransitionOverlap, getArguments, getChildFragmentManager, getContext, getDefaultViewModelCreationExtras, getDefaultViewModelProviderFactory, getEnterTransition, getExitTransition, getFragmentManager, getHost, getId, getLayoutInflater, getLayoutInflater, getLifecycle, getLoaderManager, getParentFragment, getParentFragmentManager, getReenterTransition, getResources, getRetainInstance, getReturnTransition, getSavedStateRegistry, getSharedElementEnterTransition, getSharedElementReturnTransition, getString, getString, getTag, getTargetFragment, getTargetRequestCode, getText, getUserVisibleHint, getView, getViewLifecycleOwner, getViewLifecycleOwnerLiveData, getViewModelStore, hashCode, hasOptionsMenu, instantiate, instantiate, isAdded, isDetached, isHidden, isInLayout, isMenuVisible, isRemoving, isResumed, isStateSaved, isVisible, onActivityCreated, onActivityResult, onAttach, onAttach, onAttachFragment, onConfigurationChanged, onContextItemSelected, onCreateAnimation, onCreateAnimator, onCreateContextMenu, onCreateOptionsMenu, onDestroy, onDestroyOptionsMenu, onDetach, onGetLayoutInflater, onHiddenChanged, onInflate, onInflate, onLowMemory, onMultiWindowModeChanged, onOptionsItemSelected, onOptionsMenuClosed, onPictureInPictureModeChanged, onPrepareOptionsMenu, onPrimaryNavigationFragmentChanged, onRequestPermissionsResult, onViewStateRestored, postponeEnterTransition, postponeEnterTransition, registerForActivityResult, registerForActivityResult, registerForContextMenu, requestPermissions, requireActivity, requireArguments, requireContext, requireFragmentManager, requireHost, requireParentFragment, requireView, setAllowEnterTransitionOverlap, setAllowReturnTransitionOverlap, setArguments, setEnterSharedElementCallback, setEnterTransition, setExitSharedElementCallback, setExitTransition, setHasOptionsMenu, setInitialSavedState, setMenuVisibility, setReenterTransition, setRetainInstance, setReturnTransition, setSharedElementEnterTransition, setSharedElementReturnTransition, setTargetFragment, setUserVisibleHint, shouldShowRequestPermissionRationale, startActivity, startActivity, startActivityForResult, startActivityForResult, startIntentSenderForResult, startPostponedEnterTransition, toString, unregisterForContextMenu
from java.lang.Objectclone, finalize, getClass, notify, notifyAll, wait, wait, wait

Constructors

public DetailsSupportFragment()

Methods

public void setAdapter(ObjectAdapter adapter)

Sets the list of rows for the fragment.

public ObjectAdapter getAdapter()

Returns the list of rows.

public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener)

Sets an item selection listener.

public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener)

Sets an item clicked listener.

public BaseOnItemViewClickedListener getOnItemViewClickedListener()

Returns the item clicked listener.

public void onCreate(Bundle savedInstanceState)

Called to do initial creation of a fragment. This is called after Fragment.onAttach(Activity) and before Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).

Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point. If you want to do work once the activity itself is created, add a LifecycleObserver on the activity's Lifecycle, removing it when it receives the callback.

Any restored child fragments will be created before the base Fragment.onCreate method returns.

Parameters:

savedInstanceState: If the fragment is being re-created from a previous saved state, this is the state.

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Called to have the fragment instantiate its user interface view. This is optional, and non-graphical fragments can return null. This will be called between Fragment.onCreate(Bundle) and Fragment.onViewCreated(View, Bundle).

A default View can be returned by calling Fragment.Fragment(int) in your constructor. Otherwise, this method returns null.

It is recommended to only inflate the layout in this method and move logic that operates on the returned View to Fragment.onViewCreated(View, Bundle).

If you return a View from here, you will later be called in Fragment.onDestroyView() when the view is being released.

Parameters:

inflater: The LayoutInflater object that can be used to inflate any views in the fragment,
container: If non-null, this is the parent view that the fragment's UI should be attached to. The fragment should not add the view itself, but this can be used to generate the LayoutParams of the view.
savedInstanceState: If non-null, this fragment is being re-constructed from a previous saved state as given here.

Returns:

Return the View for the fragment's UI, or null.

public void onDestroyView()

Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has been detached from the fragment. The next time the fragment needs to be displayed, a new view will be created. This is called after Fragment.onStop() and before Fragment.onDestroy(). It is called regardless of whether Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) returned a non-null view. Internally it is called after the view's state has been saved but before it has been removed from its parent.

protected View inflateTitle(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)

Deprecated: override BrandedSupportFragment.onInflateTitleView(LayoutInflater, ViewGroup, Bundle) instead.

public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)

Called by BrandedSupportFragment.installTitleView(LayoutInflater, ViewGroup, Bundle) to inflate title view. Default implementation uses layout file lb_browse_title. Subclass may override and use its own layout, the layout must have a descendant with id browse_title_group that implements . Subclass may return null if no title is needed.

Parameters:

inflater: The LayoutInflater object that can be used to inflate any views in the fragment,
parent: Parent of title view.
savedInstanceState: If non-null, this fragment is being re-constructed from a previous saved state as given here.

Returns:

Title view which must have a descendant with id browse_title_group that implements , or null for no title view.

protected void setupPresenter(Presenter rowPresenter)

Called to setup each Presenter of Adapter passed in DetailsSupportFragment.setAdapter(ObjectAdapter).Note that setup should only change the Presenter behavior that is meaningful in DetailsSupportFragment. For example how a row is aligned in details Fragment. The default implementation invokes DetailsSupportFragment.setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)

protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter)

Called to setup FullWidthDetailsOverviewRowPresenter. The default implementation adds two alignment positions(ItemAlignmentFacet) for ViewHolder of FullWidthDetailsOverviewRowPresenter to align in fragment.

public RowsSupportFragment getRowsSupportFragment()

Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment. If view of DetailsSupportFragment is not created, the method returns null.

Returns:

Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.

public void setSelectedPosition(int position)

Sets the selected row position with smooth animation.

public void setSelectedPosition(int position, boolean smooth)

Sets the selected row position.

public void onStop()

Called when the Fragment is no longer started. This is generally tied to of the containing Activity's lifecycle.

protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)

Called on every visible row to change view status when current selected row position or selected sub position changed. Subclass may override. The default implementation calls DetailsSupportFragment.onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter, FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int) if presenter is instance of FullWidthDetailsOverviewRowPresenter.

Parameters:

presenter: The presenter used to create row ViewHolder.
viewHolder: The visible (attached) row ViewHolder, note that it may or may not be selected.
adapterPosition: The adapter position of viewHolder inside adapter.
selectedPosition: The adapter position of currently selected row.
selectedSubPosition: The sub position within currently selected row. This is used When a row has multiple alignment positions.

protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter, FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)

Called to change DetailsOverviewRow view status when current selected row position or selected sub position changed. Subclass may override. The default implementation switches between three states based on the positions: FullWidthDetailsOverviewRowPresenter.STATE_HALF, FullWidthDetailsOverviewRowPresenter.STATE_FULL and FullWidthDetailsOverviewRowPresenter.STATE_SMALL.

Parameters:

presenter: The presenter used to create row ViewHolder.
viewHolder: The visible (attached) row ViewHolder, note that it may or may not be selected.
adapterPosition: The adapter position of viewHolder inside adapter.
selectedPosition: The adapter position of currently selected row.
selectedSubPosition: The sub position within currently selected row. This is used When a row has multiple alignment positions.

public void onStart()

Called when the Fragment is visible to the user. This is generally tied to of the containing Activity's lifecycle.

protected java.lang.Object createEntranceTransition()

Create entrance transition. Subclass can override to load transition from resource or construct manually. Typically app does not need to override the default transition that browse and details provides.

protected void runEntranceTransition(java.lang.Object entranceTransition)

Run entrance transition. Subclass may use TransitionManager to perform go(Scene) or beginDelayedTransition(). App should not override the default implementation of browse and details fragment.

protected void onEntranceTransitionEnd()

Callback when entrance transition is ended.

protected void onEntranceTransitionPrepare()

Callback when entrance transition is prepared. This is when fragment should stop user input and animations.

protected void onEntranceTransitionStart()

Callback when entrance transition is started. This is when fragment should stop processing layout.

public DetailsParallax getParallax()

Returns the DetailsParallax instance used by DetailsSupportFragmentBackgroundController to configure parallax effect of background and control embedded video playback. App usually does not use this method directly. App may use this method for other custom parallax tasks.

Returns:

The DetailsParallax instance attached to the DetailsSupportFragment.

Source

// CHECKSTYLE:OFF Generated code
/* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package androidx.leanback.app;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import androidx.leanback.R;
import androidx.leanback.transition.TransitionHelper;
import androidx.leanback.transition.TransitionListener;
import androidx.leanback.util.StateMachine.Event;
import androidx.leanback.util.StateMachine.State;
import androidx.leanback.widget.BaseOnItemViewClickedListener;
import androidx.leanback.widget.BaseOnItemViewSelectedListener;
import androidx.leanback.widget.BrowseFrameLayout;
import androidx.leanback.widget.DetailsParallax;
import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
import androidx.leanback.widget.ItemAlignmentFacet;
import androidx.leanback.widget.ItemBridgeAdapter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.PresenterSelector;
import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.VerticalGridView;

import java.lang.ref.WeakReference;

/**
 * A fragment for creating Leanback details screens.
 *
 * <p>
 * A DetailsSupportFragment renders the elements of its {@link ObjectAdapter} as a set
 * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
 * of {@link RowPresenter}.
 * </p>
 *
 * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsSupportFragment will
 * setup default behavior of the DetailsOverviewRow:
 * <ul>
 * <li>
 * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
 * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
 * </li>
 * <li>
 * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
 * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
 * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
 * </li>
 * </ul>
 *
 * <p>
 * The recommended activity themes to use with a DetailsSupportFragment are
 * <ul>
 * <li>
 * {@link androidx.leanback.R.style#Theme_Leanback_Details} with activity
 * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
 * </li>
 * <li>
 * {@link androidx.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
 * if shared element transition is not needed, for example if first row is not rendered by
 * {@link FullWidthDetailsOverviewRowPresenter}.
 * </li>
 * </ul>
 * </p>
 *
 * <p>
 * DetailsSupportFragment can use {@link DetailsSupportFragmentBackgroundController} to add a parallax drawable
 * background and embedded video playing fragment.
 * </p>
 */
public class DetailsSupportFragment extends BaseSupportFragment {
    static final String TAG = "DetailsSupportFragment";
    static final boolean DEBUG = false;

    final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
        @Override
        public void run() {
            mRowsSupportFragment.setEntranceTransitionState(false);
        }
    };

    final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");

    void switchToVideoBeforeVideoSupportFragmentCreated() {
        // if the video fragment is not ready: immediately fade out covering drawable,
        // hide title and mark mPendingFocusOnVideo and set focus on it later.
        mDetailsBackgroundController.switchToVideoBeforeCreate();
        showTitle(false);
        mPendingFocusOnVideo = true;
        slideOutGridView();
    }

    final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
            false, false) {
        @Override
        public void run() {
            switchToVideoBeforeVideoSupportFragmentCreated();
        }
    };

    final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
            false, false) {
        @Override
        public void run() {
            if (mWaitEnterTransitionTimeout != null) {
                mWaitEnterTransitionTimeout.mRef.clear();
            }
            // clear the activity enter/sharedElement transition, return transitions are kept.
            // keep the return transitions and clear enter transition
            if (getActivity() != null) {
                Window window = getActivity().getWindow();
                Object returnTransition = TransitionHelper.getReturnTransition(window);
                Object sharedReturnTransition = TransitionHelper
                        .getSharedElementReturnTransition(window);
                TransitionHelper.setEnterTransition(window, null);
                TransitionHelper.setSharedElementEnterTransition(window, null);
                TransitionHelper.setReturnTransition(window, returnTransition);
                TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
            }
        }
    };

    final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
            true, false);

    final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
        @Override
        public void run() {
            Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
            TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
        }
    };

    final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
        @Override
        public void run() {
            if (mWaitEnterTransitionTimeout == null) {
                new WaitEnterTransitionTimeout(DetailsSupportFragment.this);
            }
        }
    };

    /**
     * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
     * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
     */
    static final class WaitEnterTransitionTimeout implements Runnable {
        static final long WAIT_ENTERTRANSITION_START = 200;

        final WeakReference<DetailsSupportFragment> mRef;

        WaitEnterTransitionTimeout(DetailsSupportFragment f) {
            mRef = new WeakReference<>(f);
            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
        }

        @Override
        public void run() {
            DetailsSupportFragment f = mRef.get();
            if (f != null) {
                f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
            }
        }
    }

    final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
        @Override
        public void run() {
            onSafeStart();
        }
    };

    final Event EVT_ONSTART = new Event("onStart");

    final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");

    final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");

    final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");

    final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");

    @Override
    void createStateMachineStates() {
        super.createStateMachineStates();
        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
        mStateMachine.addState(STATE_ON_SAFE_START);
        mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
        mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
        mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
        mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
        mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
        mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
    }

    @Override
    void createStateMachineTransitions() {
        super.createStateMachineTransitions();
        /**
         * Part 1: Processing enter transitions after fragment.onCreate
         */
        mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
        // if transition is not supported, skip to complete
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
                COND_TRANSITION_NOT_SUPPORTED);
        // if transition is not set on Activity, skip to complete
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
                EVT_NO_ENTER_TRANSITION);
        // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
        // complete.
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
                EVT_SWITCH_TO_VIDEO);
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
        // once after onCreateView, we cannot skip the enter transition, add a listener and wait
        // it to finish
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
                EVT_ON_CREATEVIEW);
        // when enter transition finishes, go to complete, however this might never happen if
        // the activity is not giving transition options in startActivity, there is no API to query
        // if this activity is started in a enter transition mode. So we rely on a timer below:
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
                STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
        // we are expecting app to start delayed enter transition shortly after details row is
        // loaded, so create a timer and wait for enter transition start.
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
                STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
        // if enter transition not started in the timer, skip to DONE, this can be also true when
        // startActivity is not giving transition option.
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
                EVT_ENTER_TRANSIITON_DONE);

        /**
         * Part 2: modification to the entrance transition defined in BaseSupportFragment
         */
        // Must finish enter transition before perform entrance transition.
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
        // Calling switch to video would hide immediately and skip entrance transition
        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
                EVT_SWITCH_TO_VIDEO);
        mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
        // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
        // still need to do the switchToVideo.
        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
                EVT_SWITCH_TO_VIDEO);

        // for once the view is created in onStart and prepareEntranceTransition was called, we
        // could setEntranceStartState:
        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
                STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);

        /**
         * Part 3: onSafeStart()
         */
        // for onSafeStart: the condition is onStart called, entrance transition complete
        mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
    }

    private class SetSelectionRunnable implements Runnable {
        int mPosition;
        boolean mSmooth = true;

        SetSelectionRunnable() {
        }

        @Override
        public void run() {
            if (mRowsSupportFragment == null) {
                return;
            }
            mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
        }
    }

    static final class EnterTransitionListener extends TransitionListener {
        final WeakReference<DetailsSupportFragment> mFragment;

        EnterTransitionListener(DetailsSupportFragment fragment) {
            mFragment = new WeakReference<>(fragment);
        }

        @Override
        public void onTransitionStart(Object transition) {
            DetailsSupportFragment fragment = mFragment.get();
            if (fragment == null) {
                return;
            }
            if (fragment.mWaitEnterTransitionTimeout != null) {
                // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
                // when transition finishes.
                fragment.mWaitEnterTransitionTimeout.mRef.clear();
            }
        }

        @Override
        public void onTransitionCancel(Object transition) {
            DetailsSupportFragment fragment = mFragment.get();
            if (fragment == null) {
                return;
            }
            fragment.mStateMachine.fireEvent(fragment.EVT_ENTER_TRANSIITON_DONE);
        }

        @Override
        public void onTransitionEnd(Object transition) {
            DetailsSupportFragment fragment = mFragment.get();
            if (fragment == null) {
                return;
            }
            fragment.mStateMachine.fireEvent(fragment.EVT_ENTER_TRANSIITON_DONE);
        }
    }

    final TransitionListener mEnterTransitionListener = new EnterTransitionListener(this);

    static final class ReturnTransitionListener extends TransitionListener {
        final WeakReference<DetailsSupportFragment> mFragment;

        ReturnTransitionListener(DetailsSupportFragment fragment) {
            mFragment = new WeakReference<>(fragment);
        }

        @Override
        public void onTransitionStart(Object transition) {
            DetailsSupportFragment fragment = mFragment.get();
            if (fragment == null) {
                return;
            }
            fragment.onReturnTransitionStart();
        }
    }

    final TransitionListener mReturnTransitionListener = new ReturnTransitionListener(this);

    BrowseFrameLayout mRootView;
    View mBackgroundView;
    Drawable mBackgroundDrawable;
    Fragment mVideoSupportFragment;
    DetailsParallax mDetailsParallax;
    RowsSupportFragment mRowsSupportFragment;
    ObjectAdapter mAdapter;
    int mContainerListAlignTop;
    BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
    BaseOnItemViewClickedListener mOnItemViewClickedListener;
    DetailsSupportFragmentBackgroundController mDetailsBackgroundController;

    // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
    // true, we will focus to VideoSupportFragment immediately after video fragment's view is created.
    boolean mPendingFocusOnVideo = false;

    WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;

    Object mSceneAfterEntranceTransition;

    final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();

    final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
            new BaseOnItemViewSelectedListener<Object>() {
        @Override
        @SuppressWarnings("unchecked")
        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
            int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
            int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
            if (DEBUG) Log.v(TAG, "row selected position " + position
                    + " subposition " + subposition);
            onRowSelected(position, subposition);
            if (mExternalOnItemViewSelectedListener != null) {
                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
                        rowViewHolder, row);
            }
        }
    };

    /**
     * Sets the list of rows for the fragment.
     */
    public void setAdapter(ObjectAdapter adapter) {
        mAdapter = adapter;
        Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
        if (presenters != null) {
            for (int i = 0; i < presenters.length; i++) {
                setupPresenter(presenters[i]);
            }
        } else {
            Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
        }
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setAdapter(adapter);
        }
    }

    /**
     * Returns the list of rows.
     */
    public ObjectAdapter getAdapter() {
        return mAdapter;
    }

    /**
     * Sets an item selection listener.
     */
    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
        mExternalOnItemViewSelectedListener = listener;
    }

    /**
     * Sets an item clicked listener.
     */
    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
        if (mOnItemViewClickedListener != listener) {
            mOnItemViewClickedListener = listener;
            if (mRowsSupportFragment != null) {
                mRowsSupportFragment.setOnItemViewClickedListener(listener);
            }
        }
    }

    /**
     * Returns the item clicked listener.
     */
    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
        return mOnItemViewClickedListener;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContainerListAlignTop =
            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);

        FragmentActivity activity = getActivity();
        if (activity != null) {
            Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
            if (transition == null) {
                mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
            }
            transition = TransitionHelper.getReturnTransition(activity.getWindow());
            if (transition != null) {
                TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
            }
        } else {
            mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
        }
    }

    @Override
    @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        mRootView = (BrowseFrameLayout) inflater.inflate(
                R.layout.lb_details_fragment, container, false);
        mBackgroundView = mRootView.findViewById(R.id.details_background_view);
        if (mBackgroundView != null) {
            mBackgroundView.setBackground(mBackgroundDrawable);
        }
        mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
                R.id.details_rows_dock);
        if (mRowsSupportFragment == null) {
            mRowsSupportFragment = new RowsSupportFragment();
            getChildFragmentManager().beginTransaction()
                    .replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
        }
        installTitleView(inflater, mRootView, savedInstanceState);
        mRowsSupportFragment.setAdapter(mAdapter);
        mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);

        mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
            @Override
            public void run() {
                mRowsSupportFragment.setEntranceTransitionState(true);
            }
        });

        setupDpadNavigation();

        if (Build.VERSION.SDK_INT >= 21) {
            // Setup adapter listener to work with ParallaxTransition (>= API 21).
            mRowsSupportFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
                @Override
                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
                    if (mDetailsParallax != null && vh.getViewHolder()
                            instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
                        FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
                                (FullWidthDetailsOverviewRowPresenter.ViewHolder)
                                        vh.getViewHolder();
                        rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
                                mDetailsParallax);
                    }
                }
            });
        }
        return mRootView;
    }

    @Override
    public void onDestroyView() {
        if (mDetailsParallax != null) {
            mDetailsParallax.setRecyclerView(null);
        }
        mRootView = null;
        mBackgroundView = null;
        mRowsSupportFragment = null;
        mVideoSupportFragment = null;
        mSceneAfterEntranceTransition = null;
        super.onDestroyView();
    }

    /**
     * @deprecated override {@link BrandedSupportFragment#onInflateTitleView(LayoutInflater, ViewGroup, Bundle)} instead.
     */
    @Deprecated
    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
            Bundle savedInstanceState) {
        return super.onInflateTitleView(inflater, parent, savedInstanceState);
    }

    @Override
    public @NonNull View onInflateTitleView(
            @NonNull LayoutInflater inflater,
            @Nullable ViewGroup parent,
            @Nullable Bundle savedInstanceState
    ) {
        return inflateTitle(inflater, parent, savedInstanceState);
    }

    void setVerticalGridViewLayout(VerticalGridView listview) {
        // align the top edge of item to a fixed position
        listview.setItemAlignmentOffset(-mContainerListAlignTop);
        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
        listview.setWindowAlignmentOffset(0);
        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    }

    /**
     * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
     * that setup should only change the Presenter behavior that is meaningful in DetailsSupportFragment.
     * For example how a row is aligned in details Fragment.   The default implementation invokes
     * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
     *
     */
    protected void setupPresenter(Presenter rowPresenter) {
        if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
            setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
        }
    }

    /**
     * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
     * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
     * FullWidthDetailsOverviewRowPresenter to align in fragment.
     */
    protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
        ItemAlignmentFacet facet = new ItemAlignmentFacet();
        // by default align details_frame to half window height
        ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
        alignDef1.setItemAlignmentViewId(R.id.details_frame);
        alignDef1.setItemAlignmentOffset(- getResources()
                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
        alignDef1.setItemAlignmentOffsetPercent(0);
        // when description is selected, align details_frame to top edge
        ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
        alignDef2.setItemAlignmentViewId(R.id.details_frame);
        alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
        alignDef2.setItemAlignmentOffset(- getResources()
                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
        alignDef2.setItemAlignmentOffsetPercent(0);
        ItemAlignmentFacet.ItemAlignmentDef[] defs =
                new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
        facet.setAlignmentDefs(defs);
        presenter.setFacet(ItemAlignmentFacet.class, facet);
    }

    VerticalGridView getVerticalGridView() {
        return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
    }

    /**
     * Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.  If view of
     * DetailsSupportFragment is not created, the method returns null.
     * @return Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.
     */
    public RowsSupportFragment getRowsSupportFragment() {
        return mRowsSupportFragment;
    }

    /**
     * Setup dimensions that are only meaningful when the child Fragments are inside
     * DetailsSupportFragment.
     */
    private void setupChildFragmentLayout() {
        setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
    }

    /**
     * Sets the selected row position with smooth animation.
     */
    public void setSelectedPosition(int position) {
        setSelectedPosition(position, true);
    }

    /**
     * Sets the selected row position.
     */
    public void setSelectedPosition(int position, boolean smooth) {
        mSetSelectionRunnable.mPosition = position;
        mSetSelectionRunnable.mSmooth = smooth;
        if (getView() != null && getView().getHandler() != null) {
            getView().getHandler().post(mSetSelectionRunnable);
        }
    }

    void switchToVideo() {
        if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
            mVideoSupportFragment.getView().requestFocus();
        } else {
            mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
        }
    }

    void switchToRows() {
        mPendingFocusOnVideo = false;
        VerticalGridView verticalGridView = getVerticalGridView();
        if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
            verticalGridView.requestFocus();
        }
    }

    /**
     * This method asks DetailsSupportFragmentBackgroundController to add a fragment for rendering video.
     * In case the fragment is already there, it will return the existing one. The method must be
     * called after calling super.onCreate(). App usually does not call this method directly.
     *
     * @return Fragment the added or restored fragment responsible for rendering video.
     * @see DetailsSupportFragmentBackgroundController#onCreateVideoSupportFragment()
     */
    final Fragment findOrCreateVideoSupportFragment() {
        if (mVideoSupportFragment != null) {
            return mVideoSupportFragment;
        }
        Fragment fragment = getChildFragmentManager()
                .findFragmentById(R.id.video_surface_container);
        if (fragment == null && mDetailsBackgroundController != null) {
            FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
            ft2.add(androidx.leanback.R.id.video_surface_container,
                    fragment = mDetailsBackgroundController.onCreateVideoSupportFragment());
            ft2.commit();
            if (mPendingFocusOnVideo) {
                // wait next cycle for Fragment view created so we can focus on it.
                // This is a bit hack eventually we will do commitNow() which get view immediately.
                getView().post(new Runnable() {
                    @Override
                    public void run() {
                        if (getView() != null) {
                            switchToVideo();
                        }
                        mPendingFocusOnVideo = false;
                    }
                });
            }
        }
        mVideoSupportFragment = fragment;
        return mVideoSupportFragment;
    }

    void onRowSelected(int selectedPosition, int selectedSubPosition) {
        ObjectAdapter adapter = getAdapter();
        if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
                && mRowsSupportFragment.getView().hasFocus() && !mPendingFocusOnVideo)
                && (adapter == null || adapter.size() == 0
                || (getVerticalGridView().getSelectedPosition() == 0
                && getVerticalGridView().getSelectedSubPosition() == 0))) {
            showTitle(true);
        } else {
            showTitle(false);
        }
        if (adapter != null && adapter.size() > selectedPosition) {
            final VerticalGridView gridView = getVerticalGridView();
            final int count = gridView.getChildCount();
            if (count > 0) {
                mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
            }
            for (int i = 0; i < count; i++) {
                ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
                        gridView.getChildViewHolder(gridView.getChildAt(i));
                RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
                onSetRowStatus(rowPresenter,
                        rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
                        bridgeViewHolder.getAbsoluteAdapterPosition(),
                        selectedPosition, selectedSubPosition);
            }
        }
    }

    /**
     * Called when onStart and enter transition (postponed/none postponed) and entrance transition
     * are all finished.
     */
    @CallSuper
    void onSafeStart() {
        if (mDetailsBackgroundController != null) {
            mDetailsBackgroundController.onStart();
        }
    }

    @CallSuper
    void onReturnTransitionStart() {
        if (mDetailsBackgroundController != null) {
            // first disable parallax effect that auto-start PlaybackGlue.
            boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
            // if video is not visible we can safely remove VideoSupportFragment,
            // otherwise let video playing during return transition.
            if (!isVideoVisible && mVideoSupportFragment != null) {
                FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
                ft2.remove(mVideoSupportFragment);
                ft2.commit();
                mVideoSupportFragment = null;
            }
        }
    }

    @Override
    public void onStop() {
        if (mDetailsBackgroundController != null) {
            mDetailsBackgroundController.onStop();
        }
        super.onStop();
    }

    /**
     * Called on every visible row to change view status when current selected row position
     * or selected sub position changed.  Subclass may override.   The default
     * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
     * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
     * instance of {@link FullWidthDetailsOverviewRowPresenter}.
     *
     * @param presenter   The presenter used to create row ViewHolder.
     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
     *                    be selected.
     * @param adapterPosition  The adapter position of viewHolder inside adapter.
     * @param selectedPosition The adapter position of currently selected row.
     * @param selectedSubPosition The sub position within currently selected row.  This is used
     *                            When a row has multiple alignment positions.
     */
    protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
            adapterPosition, int selectedPosition, int selectedSubPosition) {
        if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
            onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
                    (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
                    adapterPosition, selectedPosition, selectedSubPosition);
        }
    }

    /**
     * Called to change DetailsOverviewRow view status when current selected row position
     * or selected sub position changed.  Subclass may override.   The default
     * implementation switches between three states based on the positions:
     * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
     * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
     * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
     *
     * @param presenter   The presenter used to create row ViewHolder.
     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
     *                    be selected.
     * @param adapterPosition  The adapter position of viewHolder inside adapter.
     * @param selectedPosition The adapter position of currently selected row.
     * @param selectedSubPosition The sub position within currently selected row.  This is used
     *                            When a row has multiple alignment positions.
     */
    protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
            FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
            int selectedPosition, int selectedSubPosition) {
        if (selectedPosition > adapterPosition) {
            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
        } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
        } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
        } else {
            presenter.setState(viewHolder,
                    FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
        }
    }

    @Override
    public void onStart() {
        super.onStart();

        setupChildFragmentLayout();
        mStateMachine.fireEvent(EVT_ONSTART);
        if (mDetailsParallax != null) {
            mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
        }
        if (mPendingFocusOnVideo) {
            slideOutGridView();
        } else if (!getView().hasFocus()) {
            mRowsSupportFragment.getVerticalGridView().requestFocus();
        }
    }

    @Override
    protected Object createEntranceTransition() {
        return TransitionHelper.loadTransition(getContext(),
                R.transition.lb_details_enter_transition);
    }

    @Override
    protected void runEntranceTransition(Object entranceTransition) {
        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
    }

    @Override
    protected void onEntranceTransitionEnd() {
        mRowsSupportFragment.onTransitionEnd();
    }

    @Override
    protected void onEntranceTransitionPrepare() {
        mRowsSupportFragment.onTransitionPrepare();
    }

    @Override
    protected void onEntranceTransitionStart() {
        mRowsSupportFragment.onTransitionStart();
    }

    /**
     * Returns the {@link DetailsParallax} instance used by
     * {@link DetailsSupportFragmentBackgroundController} to configure parallax effect of background and
     * control embedded video playback. App usually does not use this method directly.
     * App may use this method for other custom parallax tasks.
     *
     * @return The DetailsParallax instance attached to the DetailsSupportFragment.
     */
    public DetailsParallax getParallax() {
        if (mDetailsParallax == null) {
            mDetailsParallax = new DetailsParallax();
            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) {
                mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
            }
        }
        return mDetailsParallax;
    }

    /**
     * Set background drawable shown below foreground rows UI and above
     * {@link #findOrCreateVideoSupportFragment()}.
     *
     * @see DetailsSupportFragmentBackgroundController
     */
    void setBackgroundDrawable(Drawable drawable) {
        if (mBackgroundView != null) {
            mBackgroundView.setBackground(drawable);
        }
        mBackgroundDrawable = drawable;
    }

    /**
     * This method does the following
     * <ul>
     * <li>sets up focus search handling logic in the root view to enable transitioning between
     * half screen/full screen/no video mode.</li>
     *
     * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
     * transition to appropriate mode like half/full screen video.</li>
     * </ul>
     */
    void setupDpadNavigation() {
        mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {

            @Override
            public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
                return false;
            }

            @Override
            public void onRequestChildFocus(View child, View focused) {
                if (child != mRootView.getFocusedChild()) {
                    if (child.getId() == R.id.details_fragment_root) {
                        if (!mPendingFocusOnVideo) {
                            slideInGridView();
                            showTitle(true);
                        }
                    } else if (child.getId() == R.id.video_surface_container) {
                        slideOutGridView();
                        showTitle(false);
                    } else {
                        showTitle(true);
                    }
                }
            }
        });
        mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
            @Override
            public View onFocusSearch(View focused, int direction) {
                if (mRowsSupportFragment.getVerticalGridView() != null
                        && mRowsSupportFragment.getVerticalGridView().hasFocus()) {
                    if (direction == View.FOCUS_UP) {
                        if (mDetailsBackgroundController != null
                                && mDetailsBackgroundController.canNavigateToVideoSupportFragment()
                                && mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
                            return mVideoSupportFragment.getView();
                        } else if (getTitleView() != null && getTitleView().hasFocusable()) {
                            return getTitleView();
                        }
                    }
                } else if (getTitleView() != null && getTitleView().hasFocus()) {
                    if (direction == View.FOCUS_DOWN) {
                        if (mRowsSupportFragment.getVerticalGridView() != null) {
                            return mRowsSupportFragment.getVerticalGridView();
                        }
                    }
                }
                return focused;
            }
        });

        // If we press BACK on remote while in full screen video mode, we should
        // transition back to half screen video playback mode.
        mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // This is used to check if we are in full screen video mode. This is somewhat
                // hacky and relies on the behavior of the video helper class to update the
                // focusability of the video surface view.
                if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
                        && mVideoSupportFragment.getView().hasFocus()) {
                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
                        if (getVerticalGridView().getChildCount() > 0) {
                            getVerticalGridView().requestFocus();
                            return true;
                        }
                    }
                }

                return false;
            }
        });
    }

    /**
     * Slides vertical grid view (displaying media item details) out of the screen from below.
     */
    void slideOutGridView() {
        if (getVerticalGridView() != null) {
            getVerticalGridView().animateOut();
        }
    }

    void slideInGridView() {
        if (getVerticalGridView() != null) {
            getVerticalGridView().animateIn();
        }
    }
}