public class

DetailsFragment

extends BaseFragment

 java.lang.Object

↳Fragment

androidx.leanback.app.BrandedFragment

androidx.leanback.app.BaseFragment

↳androidx.leanback.app.DetailsFragment

Gradle dependencies

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

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

Artifact androidx.leanback:leanback:1.2.0-alpha02 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.DetailsFragment android.support.v17.leanback.app.DetailsFragment

Overview

A fragment for creating Leanback details screens.

A DetailsFragment 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, DetailsFragment will setup default behavior of the DetailsOverviewRow:

The recommended activity themes to use with a DetailsFragment are

DetailsFragment can use DetailsFragmentBackgroundController to add a parallax drawable background and embedded video playing fragment.

Summary

Constructors
publicDetailsFragment()

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 DetailsFragmentBackgroundController to configure parallax effect of background and control embedded video playback.

public RowsFragmentgetRowsFragment()

Gets embedded RowsFragment showing multiple rows for DetailsFragment.

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

public voidonCreate(Bundle savedInstanceState)

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

public voidonDestroyView()

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 BrandedFragment.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()

public voidonStop()

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 DetailsFragment.setAdapter(ObjectAdapter).Note that setup should only change the Presenter behavior that is meaningful in DetailsFragment.

from BaseFragmentgetProgressBarManager, onViewCreated, prepareEntranceTransition, startEntranceTransition
from BrandedFragmentgetBadgeDrawable, getSearchAffordanceColor, getSearchAffordanceColors, getTitle, getTitleView, getTitleViewAdapter, installTitleView, isShowingTitle, onPause, onResume, onSaveInstanceState, setBadgeDrawable, setOnSearchClickedListener, setSearchAffordanceColor, setSearchAffordanceColors, setTitle, setTitleView, showTitle, showTitle
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public DetailsFragment()

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)

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

public void onDestroyView()

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

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

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

Called by BrandedFragment.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 DetailsFragment.setAdapter(ObjectAdapter).Note that setup should only change the Presenter behavior that is meaningful in DetailsFragment. For example how a row is aligned in details Fragment. The default implementation invokes DetailsFragment.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 RowsFragment getRowsFragment()

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

Returns:

Embedded RowsFragment showing multiple rows for DetailsFragment.

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()

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 DetailsFragment.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()

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 DetailsFragmentBackgroundController 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 DetailsFragment.

Source

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

// 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 android.app.Fragment;
import android.app.Activity;
import android.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 DetailsFragment 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,  DetailsFragment 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 DetailsFragment 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>
 * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
 * background and embedded video playing fragment.
 * </p>
 * @deprecated use {@link DetailsSupportFragment}
 */
@Deprecated
public class DetailsFragment extends BaseFragment {
    static final String TAG = "DetailsFragment";
    static final boolean DEBUG = false;

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

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

    void switchToVideoBeforeVideoFragmentCreated() {
        // 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() {
            switchToVideoBeforeVideoFragmentCreated();
        }
    };

    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(DetailsFragment.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<DetailsFragment> mRef;

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

        @Override
        public void run() {
            DetailsFragment 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 BaseFragment
         */
        // 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 (mRowsFragment == null) {
                return;
            }
            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
        }
    }

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

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

        @Override
        public void onTransitionStart(Object transition) {
            DetailsFragment 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) {
            DetailsFragment fragment = mFragment.get();
            if (fragment == null) {
                return;
            }
            fragment.mStateMachine.fireEvent(fragment.EVT_ENTER_TRANSIITON_DONE);
        }

        @Override
        public void onTransitionEnd(Object transition) {
            DetailsFragment 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<DetailsFragment> mFragment;

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

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

    final TransitionListener mReturnTransitionListener = new ReturnTransitionListener(this);

    BrowseFrameLayout mRootView;
    View mBackgroundView;
    Drawable mBackgroundDrawable;
    Fragment mVideoFragment;
    DetailsParallax mDetailsParallax;
    RowsFragment mRowsFragment;
    ObjectAdapter mAdapter;
    int mContainerListAlignTop;
    BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
    BaseOnItemViewClickedListener mOnItemViewClickedListener;
    DetailsFragmentBackgroundController mDetailsBackgroundController;

    // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
    // true, we will focus to VideoFragment 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 = mRowsFragment.getVerticalGridView().getSelectedPosition();
            int subposition = mRowsFragment.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 (mRowsFragment != null) {
            mRowsFragment.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 (mRowsFragment != null) {
                mRowsFragment.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);

        Activity 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
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            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);
        }
        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
                R.id.details_rows_dock);
        if (mRowsFragment == null) {
            mRowsFragment = new RowsFragment();
            getChildFragmentManager().beginTransaction()
                    .replace(R.id.details_rows_dock, mRowsFragment).commit();
        }
        installTitleView(inflater, mRootView, savedInstanceState);
        mRowsFragment.setAdapter(mAdapter);
        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);

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

        setupDpadNavigation();

        if (Build.VERSION.SDK_INT >= 21) {
            // Setup adapter listener to work with ParallaxTransition (>= API 21).
            mRowsFragment.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;
        mRowsFragment = null;
        mVideoFragment = null;
        mSceneAfterEntranceTransition = null;
        super.onDestroyView();
    }

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

    @Override
    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
                                   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 DetailsFragment.
     * 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 mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
    }

    /**
     * Gets embedded RowsFragment showing multiple rows for DetailsFragment.  If view of
     * DetailsFragment is not created, the method returns null.
     * @return Embedded RowsFragment showing multiple rows for DetailsFragment.
     */
    public RowsFragment getRowsFragment() {
        return mRowsFragment;
    }

    /**
     * Setup dimensions that are only meaningful when the child Fragments are inside
     * DetailsFragment.
     */
    private void setupChildFragmentLayout() {
        setVerticalGridViewLayout(mRowsFragment.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 (mVideoFragment != null && mVideoFragment.getView() != null) {
            mVideoFragment.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 DetailsFragmentBackgroundController 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 DetailsFragmentBackgroundController#onCreateVideoFragment()
     */
    final Fragment findOrCreateVideoFragment() {
        if (mVideoFragment != null) {
            return mVideoFragment;
        }
        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.onCreateVideoFragment());
            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;
                    }
                });
            }
        }
        mVideoFragment = fragment;
        return mVideoFragment;
    }

    void onRowSelected(int selectedPosition, int selectedSubPosition) {
        ObjectAdapter adapter = getAdapter();
        if (( mRowsFragment != null && mRowsFragment.getView() != null
                && mRowsFragment.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 VideoFragment,
            // otherwise let video playing during return transition.
            if (!isVideoVisible && mVideoFragment != null) {
                FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
                ft2.remove(mVideoFragment);
                ft2.commit();
                mVideoFragment = 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(mRowsFragment.getVerticalGridView());
        }
        if (mPendingFocusOnVideo) {
            slideOutGridView();
        } else if (!getView().hasFocus()) {
            mRowsFragment.getVerticalGridView().requestFocus();
        }
    }

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

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

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

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

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

    /**
     * Returns the {@link DetailsParallax} instance used by
     * {@link DetailsFragmentBackgroundController} 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 DetailsFragment.
     */
    public DetailsParallax getParallax() {
        if (mDetailsParallax == null) {
            mDetailsParallax = new DetailsParallax();
            if (mRowsFragment != null && mRowsFragment.getView() != null) {
                mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
            }
        }
        return mDetailsParallax;
    }

    /**
     * Set background drawable shown below foreground rows UI and above
     * {@link #findOrCreateVideoFragment()}.
     *
     * @see DetailsFragmentBackgroundController
     */
    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 (mRowsFragment.getVerticalGridView() != null
                        && mRowsFragment.getVerticalGridView().hasFocus()) {
                    if (direction == View.FOCUS_UP) {
                        if (mDetailsBackgroundController != null
                                && mDetailsBackgroundController.canNavigateToVideoFragment()
                                && mVideoFragment != null && mVideoFragment.getView() != null) {
                            return mVideoFragment.getView();
                        } else if (getTitleView() != null && getTitleView().hasFocusable()) {
                            return getTitleView();
                        }
                    }
                } else if (getTitleView() != null && getTitleView().hasFocus()) {
                    if (direction == View.FOCUS_DOWN) {
                        if (mRowsFragment.getVerticalGridView() != null) {
                            return mRowsFragment.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 (mVideoFragment != null && mVideoFragment.getView() != null
                        && mVideoFragment.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();
        }
    }
}