Subclasses:
VideoSupportFragment
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.PlaybackSupportFragment android.support.v17.leanback.app.PlaybackSupportFragment
Overview
A fragment for displaying playback controls and related content.
 
 A PlaybackSupportFragment renders the elements of its ObjectAdapter as a set
 of rows in a vertical list.  The Adapter's PresenterSelector must maintain subclasses
 of RowPresenter.
 
 
 A playback row is a row rendered by PlaybackRowPresenter.
 App can call PlaybackSupportFragment.setPlaybackRow(Row) to set playback row for the first element of adapter.
 App can call PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter) to set presenter for it.
 PlaybackSupportFragment.setPlaybackRow(Row) and PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter) are
 optional, app can pass playback row and PlaybackRowPresenter in the adapter using
 PlaybackSupportFragment.setAdapter(ObjectAdapter).
 
 
 Hiding and showing controls: the controls are initially visible and automatically show/hide
 when play/pause or user interacts with fragment.
 
Summary
| Methods | 
|---|
| public void | fadeOut() 
 Fades out the playback overlay immediately. | 
| public ObjectAdapter | getAdapter() 
 | 
| public int | getBackgroundType() 
 Returns the background type. | 
| public PlaybackSupportFragment.OnFadeCompleteListener | getFadeCompleteListener() 
 Returns the listener to be called when fade in or out has completed. | 
| public ProgressBarManager | getProgressBarManager() 
 Returns the ProgressBarManager that will show or hide progress bar in
 PlaybackSupportFragment.onBufferingStateChanged(boolean). | 
| public void | hideControlsOverlay(boolean runAnimation) 
 Hide controls overlay. | 
| public boolean | isControlsOverlayAutoHideEnabled() 
 Returns true if controls will be auto hidden after a delay when fragment is resumed. | 
| public boolean | isControlsOverlayVisible() 
 Returns true if controls overlay is visible, false otherwise. | 
| public boolean | isFadingEnabled() 
 | 
| public boolean | isShowOrHideControlsOverlayOnUserInteraction() 
 Returns true if showing and auto-hiding controls when user interacts; false otherwise. | 
| public void | notifyPlaybackRowChanged() 
 Updates the ui when the row data changes. | 
| protected void | onBufferingStateChanged(boolean start) 
 Called when media has start or stop buffering. | 
| public void | onCreate(Bundle savedInstanceState) 
 Called to do initial creation of a fragment. | 
| public View | onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
 Called to have the fragment instantiate its user interface view. | 
| public void | onDestroy() 
 Called when the fragment is no longer in use. | 
| public void | onDestroyView() 
 Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has
 been detached from the fragment. | 
| protected void | onError(int errorCode, java.lang.CharSequence errorMessage) 
 Called when media has error. | 
| public void | onPause() 
 Called when the Fragment is no longer resumed. | 
| public void | onResume() 
 Called when the fragment is visible to the user and actively running. | 
| public void | onStart() 
 Called when the Fragment is visible to the user. | 
| public void | onStop() 
 Called when the Fragment is no longer started. | 
| protected void | onVideoSizeChanged(int videoWidth, int videoHeight) 
 Called when size of the video changes. | 
| public void | onViewCreated(View view, Bundle savedInstanceState) 
 Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
 has returned, but before any saved state has been restored in to the view. | 
| public void | resetFocus() 
 Resets the focus on the button in the middle of control row. | 
| public void | setAdapter(ObjectAdapter adapter) 
 Sets the list of rows for the fragment. | 
| public void | setBackgroundType(int type) 
 Sets the background type. | 
| public void | setControlsOverlayAutoHideEnabled(boolean enabled) 
 Enables or disables auto hiding controls overlay after a short delay fragment is resumed. | 
| public void | setFadeCompleteListener(PlaybackSupportFragment.OnFadeCompleteListener listener) 
 Sets the listener to be called when fade in or out has completed. | 
| public void | setFadingEnabled(boolean enabled) 
 | 
| public void | setHostCallback(PlaybackGlueHost.HostCallback hostCallback) 
 Sets the . | 
| public void | setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) 
 This listener is called every time there is a click in RowsSupportFragment. | 
| public void | setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) 
 This listener is called every time there is a selection in RowsSupportFragment. | 
| public final void | setOnKeyInterceptListener(View.OnKeyListener handler) 
 Sets the input event handler. | 
| public void | setOnPlaybackItemViewClickedListener(BaseOnItemViewClickedListener listener) 
 Sets the BaseOnItemViewClickedListener that would be invoked for clicks
 only on PlaybackRowPresenter.ViewHolder. | 
| public void | setPlaybackRow(Row row) 
 Sets the playback row for the playback controls. | 
| public void | setPlaybackRowPresenter(PlaybackRowPresenter presenter) 
 Sets the presenter for rendering the playback row set by PlaybackSupportFragment.setPlaybackRow(Row). | 
| public void | setPlaybackSeekUiClient(PlaybackSeekUi.Client client) 
 Interface to be implemented by UI widget to support PlaybackSeekUi. | 
| 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 | setShowOrHideControlsOverlayOnUserInteraction(boolean showOrHideControlsOverlayOnUserInteraction) 
 Enables or disables showing and auto-hiding controls when user interacts. | 
| public void | showControlsOverlay(boolean runAnimation) 
 Show controls overlay. | 
| public void | tickle() 
 Tickles the playback controls. | 
| from Fragment | dump, 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, onDestroyOptionsMenu, onDetach, onGetLayoutInflater, onHiddenChanged, onInflate, onInflate, onLowMemory, onMultiWindowModeChanged, onOptionsItemSelected, onOptionsMenuClosed, onPictureInPictureModeChanged, onPrepareOptionsMenu, onPrimaryNavigationFragmentChanged, onRequestPermissionsResult, onSaveInstanceState, 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.Object | clone, finalize, getClass, notify, notifyAll, wait, wait, wait | 
Fields
public static final int 
BG_NONENo background.
public static final int 
BG_DARKA dark translucent background.
public static final int 
BG_LIGHTA light translucent background.
Constructors
public 
PlaybackSupportFragment()
Methods
Resets the focus on the button in the middle of control row.
public void 
setShowOrHideControlsOverlayOnUserInteraction(boolean showOrHideControlsOverlayOnUserInteraction)
Enables or disables showing and auto-hiding controls when user interacts. Enabled by default.
 Auto-hide timer length is defined by .
public boolean 
isShowOrHideControlsOverlayOnUserInteraction()
Returns true if showing and auto-hiding controls when user interacts; false otherwise.
public void 
setControlsOverlayAutoHideEnabled(boolean enabled)
Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
 If enabled and fragment is resumed, the view will fade out after a time period.
 User interaction will kill the timer, next time fragment is resumed,
 the timer will be started again if PlaybackSupportFragment.isControlsOverlayAutoHideEnabled() is true.
  
  In most cases app should not directly call setControlsOverlayAutoHideEnabled() as it's
  called by PlaybackBaseControlGlue on play or pause.
public boolean 
isControlsOverlayAutoHideEnabled()
Returns true if controls will be auto hidden after a delay when fragment is resumed.
public void 
setFadingEnabled(boolean enabled)
Deprecated: Uses PlaybackSupportFragment.setControlsOverlayAutoHideEnabled(boolean)
public boolean 
isFadingEnabled()
Deprecated: Uses PlaybackSupportFragment.isControlsOverlayAutoHideEnabled()
Sets the listener to be called when fade in or out has completed.
Returns the listener to be called when fade in or out has completed.
public final void 
setOnKeyInterceptListener(View.OnKeyListener handler)
Sets the input event handler.
Tickles the playback controls. Fades in the view if it was faded out. PlaybackSupportFragment.tickle() will
 kill and re-create a timer if  is
 positive.
  
  In most cases app does not need call tickle() as it's automatically called on user
  interactions.
public void 
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
 has returned, but before any saved state has been restored in to the view.
 This gives subclasses a chance to initialize themselves once
 they know their view hierarchy has been completely created.  The fragment's
 view hierarchy is not however attached to its parent at this point.
Parameters:
view: The View returned by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
savedInstanceState: If non-null, this fragment is being re-constructed
 from a previous saved state as given here.
Called when the fragment is visible to the user and actively running.
 This is generally
 tied to  of the containing
 Activity's lifecycle.
Deprecated: Call PlaybackSupportFragment.hideControlsOverlay(boolean)
Fades out the playback overlay immediately.
public void 
showControlsOverlay(boolean runAnimation)
Show controls overlay.
Parameters:
runAnimation: True to run animation, false otherwise.
public boolean 
isControlsOverlayVisible()
Returns true if controls overlay is visible, false otherwise.
Returns:
True if controls overlay is visible, false otherwise.
See also: PlaybackSupportFragment.showControlsOverlay(boolean), PlaybackSupportFragment.hideControlsOverlay(boolean)
public void 
hideControlsOverlay(boolean runAnimation)
Hide controls overlay.
Parameters:
runAnimation: True to run animation, false otherwise.
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 
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 void 
setBackgroundType(int type)
Sets the background type.
Parameters:
type: One of BG_LIGHT, BG_DARK, or BG_NONE.
public int 
getBackgroundType()
Returns the background type.
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.
Sets the . Implementor of this interface will
 take appropriate actions to take action when the hosting fragment starts/stops processing.
Called when the Fragment is visible to the user.  This is generally
 tied to  of the containing
 Activity's lifecycle.
Called when the Fragment is no longer started.  This is generally
 tied to  of the containing
 Activity's lifecycle.
Called when the Fragment is no longer resumed.  This is generally
 tied to  of the containing
 Activity's lifecycle.
This listener is called every time there is a selection in RowsSupportFragment. This can
 be used by users to take additional actions such as animations.
This listener is called every time there is a click in RowsSupportFragment. This can
 be used by users to take additional actions such as animations.
Sets the BaseOnItemViewClickedListener that would be invoked for clicks
 only on PlaybackRowPresenter.ViewHolder.
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.
Called when the fragment is no longer in use.  This is called
 after Fragment.onStop() and before Fragment.onDetach().
public void 
setPlaybackRow(
Row row)
Sets the playback row for the playback controls. The row will be set as first element
 of adapter if the adapter is ArrayObjectAdapter or SparseArrayObjectAdapter.
Parameters:
row: The row that represents the playback.
Sets the presenter for rendering the playback row set by PlaybackSupportFragment.setPlaybackRow(Row). If
 adapter does not set a PresenterSelector, PlaybackSupportFragment.setAdapter(ObjectAdapter) will
 create a ClassPresenterSelector by default and map from the row object class to this
 PlaybackRowPresenter.
Parameters:
presenter: Presenter used to render PlaybackSupportFragment.setPlaybackRow(Row).
public void 
notifyPlaybackRowChanged()
Updates the ui when the row data changes.
Sets the list of rows for the fragment. A default ClassPresenterSelector will be
 created if ObjectAdapter.getPresenterSelector() is null. if user provides
 PlaybackSupportFragment.setPlaybackRow(Row) and PlaybackSupportFragment.setPlaybackRowPresenter(PlaybackRowPresenter),
 the row and presenter will be set onto the adapter.
Parameters:
adapter: The adapter that contains related rows and optional playback row.
Interface to be implemented by UI widget to support PlaybackSeekUi.
protected void 
onVideoSizeChanged(int videoWidth, int videoHeight)
Called when size of the video changes. App may override.
Parameters:
videoWidth: Intrinsic width of video
videoHeight: Intrinsic height of video
protected void 
onBufferingStateChanged(boolean start)
Called when media has start or stop buffering. App may override. The default initial state
 is not buffering.
Parameters:
start: True for buffering start, false otherwise.
protected void 
onError(int errorCode, java.lang.CharSequence errorMessage)
Called when media has error. App may override.
Parameters:
errorCode: Optional error code for specific implementation.
errorMessage: Optional error message for specific implementation.
Returns the ProgressBarManager that will show or hide progress bar in
 PlaybackSupportFragment.onBufferingStateChanged(boolean).
Returns:
The ProgressBarManager that will show or hide progress bar in
 PlaybackSupportFragment.onBufferingStateChanged(boolean).
Source
/*
 * Copyright (C) 2016 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.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.TypedValue;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.fragment.app.Fragment;
import androidx.leanback.R;
import androidx.leanback.animation.LogAccelerateInterpolator;
import androidx.leanback.animation.LogDecelerateInterpolator;
import androidx.leanback.media.PlaybackGlueHost;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.BaseOnItemViewClickedListener;
import androidx.leanback.widget.BaseOnItemViewSelectedListener;
import androidx.leanback.widget.ClassPresenterSelector;
import androidx.leanback.widget.ItemAlignmentFacet;
import androidx.leanback.widget.ItemBridgeAdapter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.PlaybackRowPresenter;
import androidx.leanback.widget.PlaybackSeekDataProvider;
import androidx.leanback.widget.PlaybackSeekUi;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.PresenterSelector;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.SparseArrayObjectAdapter;
import androidx.leanback.widget.VerticalGridView;
import androidx.recyclerview.widget.RecyclerView;
/**
 * A fragment for displaying playback controls and related content.
 *
 * <p>
 * A PlaybackSupportFragment 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>
 * <p>
 * A playback row is a row rendered by {@link PlaybackRowPresenter}.
 * App can call {@link #setPlaybackRow(Row)} to set playback row for the first element of adapter.
 * App can call {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to set presenter for it.
 * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} are
 * optional, app can pass playback row and PlaybackRowPresenter in the adapter using
 * {@link #setAdapter(ObjectAdapter)}.
 * </p>
 * <p>
 * Hiding and showing controls: the controls are initially visible and automatically show/hide
 * when play/pause or user interacts with fragment.
 * <ul>
 *     <li>
 *         App may manually call {@link #showControlsOverlay(boolean)} or
 *         {@link #hideControlsOverlay(boolean)} to show or hide the controls.
 *     </li>
 *     <li>
 *         The controls are visible by default upon onViewCreated(). To make it initially invisible,
 *         call hideControlsOverlay(false) in overridden onViewCreated().
 *     </li>
 *     <li>
 *         Upon play or pause, PlaybackControlGlue or PlaybackTransportControlGlue will fade-in
 *         the controls and automatically fade out after a delay customized by
 *         {@link R.attr#playbackControlsAutoHideTimeout}. To disable the fade in and fade out
 *         behavior: call {@link androidx.leanback.media.PlaybackBaseControlGlue
 *         #setControlsOverlayAutoHideEnabled(boolean)} with false.
 *     </li>
 *     <li>
 *         Upon user interaction event, fragment will fade-in the controls and automatically fade
 *         out after a delay customized by {@link R.attr#playbackControlsAutoHideTickleTimeout}.
 *         To disable the fade in and fade out behavior, call {@link
 *         #setShowOrHideControlsOverlayOnUserInteraction} with false.
 *     </li>
 * </ul>
 */
public class PlaybackSupportFragment extends Fragment {
    static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
    /**
     * No background.
     */
    public static final int BG_NONE = 0;
    /**
     * A dark translucent background.
     */
    public static final int BG_DARK = 1;
    PlaybackGlueHost.HostCallback mHostCallback;
    PlaybackSeekUi.Client mSeekUiClient;
    boolean mInSeek;
    ProgressBarManager mProgressBarManager = new ProgressBarManager();
    /**
     * Resets the focus on the button in the middle of control row.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public void resetFocus() {
        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
                .findViewHolderForAdapterPosition(0);
        if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
            ((PlaybackRowPresenter) vh.getPresenter()).onReappear(
                    (RowPresenter.ViewHolder) vh.getViewHolder());
        }
    }
    private class SetSelectionRunnable implements Runnable {
        int mPosition;
        boolean mSmooth = true;
        SetSelectionRunnable() {
        }
        @Override
        public void run() {
            if (mRowsSupportFragment == null) {
                return;
            }
            mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
        }
    }
    /**
     * A light translucent background.
     */
    public static final int BG_LIGHT = 2;
    RowsSupportFragment mRowsSupportFragment;
    ObjectAdapter mAdapter;
    PlaybackRowPresenter mPresenter;
    Row mRow;
    BaseOnItemViewSelectedListener mExternalItemSelectedListener;
    BaseOnItemViewClickedListener mExternalItemClickedListener;
    BaseOnItemViewClickedListener mPlaybackItemClickedListener;
    private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
            new BaseOnItemViewClickedListener() {
                @Override
                @SuppressWarnings("unchecked")
                public void onItemClicked(Presenter.ViewHolder itemViewHolder,
                                          Object item,
                                          RowPresenter.ViewHolder rowViewHolder,
                                          Object row) {
                    if (mPlaybackItemClickedListener != null
                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
                        mPlaybackItemClickedListener.onItemClicked(
                                itemViewHolder, item, rowViewHolder, row);
                    }
                    if (mExternalItemClickedListener != null) {
                        mExternalItemClickedListener.onItemClicked(
                                itemViewHolder, item, rowViewHolder, row);
                    }
                }
            };
    private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
            new BaseOnItemViewSelectedListener() {
                @Override
                @SuppressWarnings("unchecked")
                public void onItemSelected(Presenter.ViewHolder itemViewHolder,
                                           Object item,
                                           RowPresenter.ViewHolder rowViewHolder,
                                           Object row) {
                    if (mExternalItemSelectedListener != null) {
                        mExternalItemSelectedListener.onItemSelected(
                                itemViewHolder, item, rowViewHolder, row);
                    }
                }
            };
    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
    public ObjectAdapter getAdapter() {
        return mAdapter;
    }
    /**
     * Listener allowing the application to receive notification of fade in and/or fade out
     * completion events.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static class OnFadeCompleteListener {
        public void onFadeInComplete() {
        }
        public void onFadeOutComplete() {
        }
    }
    private static final String TAG = "PlaybackSupportFragment";
    private static final boolean DEBUG = false;
    private static final int ANIMATION_MULTIPLIER = 1;
    private static final int START_FADE_OUT = 1;
    int mPaddingBottom;
    int mOtherRowsCenterToBottom;
    View mRootView;
    View mBackgroundView;
    int mBackgroundType = BG_DARK;
    int mBgDarkColor;
    int mBgLightColor;
    int mAutohideTimerAfterPlayingInMs;
    int mAutohideTimerAfterTickleInMs;
    int mMajorFadeTranslateY, mMinorFadeTranslateY;
    int mAnimationTranslateY;
    OnFadeCompleteListener mFadeCompleteListener;
    View.OnKeyListener mInputEventHandler;
    boolean mFadingEnabled = true;
    boolean mControlVisibleBeforeOnCreateView = true;
    boolean mControlVisible = true;
    boolean mShowOrHideControlsOverlayOnUserInteraction = true;
    int mBgAlpha;
    ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
    ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
    ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
    private final Animator.AnimatorListener mFadeListener =
            new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    enableVerticalGridAnimations(false);
                }
                @Override
                public void onAnimationRepeat(Animator animation) {
                }
                @Override
                public void onAnimationCancel(Animator animation) {
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
                    if (mBgAlpha > 0) {
                        enableVerticalGridAnimations(true);
                        if (mFadeCompleteListener != null) {
                            mFadeCompleteListener.onFadeInComplete();
                        }
                    } else {
                        VerticalGridView verticalView = getVerticalGridView();
                        // reset focus to the primary actions only if the selected row was the controls row
                        if (verticalView != null && verticalView.getSelectedPosition() == 0) {
                            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
                                    verticalView.findViewHolderForAdapterPosition(0);
                            if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
                                ((PlaybackRowPresenter)vh.getPresenter()).onReappear(
                                        (RowPresenter.ViewHolder) vh.getViewHolder());
                            }
                        }
                        if (mFadeCompleteListener != null) {
                            mFadeCompleteListener.onFadeOutComplete();
                        }
                    }
                }
            };
    public PlaybackSupportFragment() {
        mProgressBarManager.setInitialDelay(500);
    }
    VerticalGridView getVerticalGridView() {
        if (mRowsSupportFragment == null) {
            return null;
        }
        return mRowsSupportFragment.getVerticalGridView();
    }
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            if (message.what == START_FADE_OUT && mFadingEnabled) {
                hideControlsOverlay(true);
            }
        }
    };
    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
            new VerticalGridView.OnTouchInterceptListener() {
                @Override
                public boolean onInterceptTouchEvent(MotionEvent event) {
                    return onInterceptInputEvent(event);
                }
            };
    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
            new VerticalGridView.OnKeyInterceptListener() {
                @Override
                public boolean onInterceptKeyEvent(KeyEvent event) {
                    return onInterceptInputEvent(event);
                }
            };
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void setBgAlpha(int alpha) {
        mBgAlpha = alpha;
        if (mBackgroundView != null) {
            mBackgroundView.getBackground().setAlpha(alpha);
        }
    }
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void enableVerticalGridAnimations(boolean enable) {
        if (getVerticalGridView() != null) {
            getVerticalGridView().setAnimateChildLayout(enable);
        }
    }
    /**
     * Enables or disables showing and auto-hiding controls when user interacts. Enabled by default.
     * Auto-hide timer length is defined by {@link R.attr#playbackControlsAutoHideTickleTimeout}.
     */
    public void setShowOrHideControlsOverlayOnUserInteraction(boolean
            showOrHideControlsOverlayOnUserInteraction) {
        mShowOrHideControlsOverlayOnUserInteraction = showOrHideControlsOverlayOnUserInteraction;
    }
    /**
     * Returns true if showing and auto-hiding controls when user interacts; false otherwise.
     */
    public boolean isShowOrHideControlsOverlayOnUserInteraction() {
        return mShowOrHideControlsOverlayOnUserInteraction;
    }
    /**
     * Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
     * If enabled and fragment is resumed, the view will fade out after a time period.
     * User interaction will kill the timer, next time fragment is resumed,
     * the timer will be started again if {@link #isControlsOverlayAutoHideEnabled()} is true.
     *  <p>
     *  In most cases app should not directly call setControlsOverlayAutoHideEnabled() as it's
     *  called by {@link androidx.leanback.media.PlaybackBaseControlGlue} on play or pause.
     */
    public void setControlsOverlayAutoHideEnabled(boolean enabled) {
        if (DEBUG) Log.v(TAG, "setControlsOverlayAutoHideEnabled " + enabled);
        if (enabled != mFadingEnabled) {
            mFadingEnabled = enabled;
            if (isResumed() && getView().hasFocus()) {
                showControlsOverlay(true);
                if (enabled) {
                    // StateGraph 7->2 5->2
                    startFadeTimer(mAutohideTimerAfterPlayingInMs);
                } else {
                    // StateGraph 4->5 2->5
                    stopFadeTimer();
                }
            } else {
                // StateGraph 6->1 1->6
            }
        }
    }
    /**
     * Returns true if controls will be auto hidden after a delay when fragment is resumed.
     */
    public boolean isControlsOverlayAutoHideEnabled() {
        return mFadingEnabled;
    }
    /**
     * @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
     */
    @Deprecated
    public void setFadingEnabled(boolean enabled) {
        setControlsOverlayAutoHideEnabled(enabled);
    }
    /**
     * @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
     */
    @Deprecated
    public boolean isFadingEnabled() {
        return isControlsOverlayAutoHideEnabled();
    }
    /**
     * Sets the listener to be called when fade in or out has completed.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
        mFadeCompleteListener = listener;
    }
    /**
     * Returns the listener to be called when fade in or out has completed.
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public OnFadeCompleteListener getFadeCompleteListener() {
        return mFadeCompleteListener;
    }
    /**
     * Sets the input event handler.
     */
    public final void setOnKeyInterceptListener(View.OnKeyListener handler) {
        mInputEventHandler = handler;
    }
    /**
     * Tickles the playback controls. Fades in the view if it was faded out. {@link #tickle()} will
     * kill and re-create a timer if {@link R.attr#playbackControlsAutoHideTickleTimeout} is
     * positive.
     *  <p>
     *  In most cases app does not need call tickle() as it's automatically called on user
     *  interactions.
     */
    public void tickle() {
        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
        //StateGraph 2->4
        stopFadeTimer();
        showControlsOverlay(true);
        // Optionally start fading out timer if it's currently playing (mFadingEnabled is true)
        if (mAutohideTimerAfterTickleInMs > 0 && mFadingEnabled) {
            startFadeTimer(mAutohideTimerAfterTickleInMs);
        }
    }
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    boolean onInterceptInputEvent(InputEvent event) {
        final boolean controlsHidden = !mControlVisible;
        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
        boolean consumeEvent = false;
        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
        int keyAction = 0;
        if (event instanceof KeyEvent) {
            keyCode = ((KeyEvent) event).getKeyCode();
            keyAction = ((KeyEvent) event).getAction();
            if (mInputEventHandler != null) {
                consumeEvent = mInputEventHandler.onKey(getView(), keyCode, (KeyEvent) event);
            }
        }
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_DPAD_DOWN:
            case KeyEvent.KEYCODE_DPAD_UP:
            case KeyEvent.KEYCODE_DPAD_LEFT:
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                // Event may be consumed; regardless, if controls are hidden then these keys will
                // bring up the controls.
                if (controlsHidden) {
                    consumeEvent = true;
                }
                if (mShowOrHideControlsOverlayOnUserInteraction
                        && keyAction == KeyEvent.ACTION_DOWN) {
                    tickle();
                }
                break;
            case KeyEvent.KEYCODE_BACK:
            case KeyEvent.KEYCODE_ESCAPE:
                if (mInSeek) {
                    // when in seek, the SeekUi will handle the BACK.
                    return false;
                }
                // If controls are not hidden, back will be consumed to fade
                // them out (even if the key was consumed by the handler).
                if (mShowOrHideControlsOverlayOnUserInteraction && !controlsHidden) {
                    consumeEvent = true;
                    if (((KeyEvent) event).getAction() == KeyEvent.ACTION_UP) {
                        hideControlsOverlay(true);
                    }
                }
                break;
            default:
                if (mShowOrHideControlsOverlayOnUserInteraction && consumeEvent) {
                    if (keyAction == KeyEvent.ACTION_DOWN) {
                        tickle();
                    }
                }
        }
        return consumeEvent;
    }
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // controls view are initially visible, make it invisible
        // if app has called hideControlsOverlay() before view created.
        mControlVisible = true;
        if (!mControlVisibleBeforeOnCreateView) {
            showControlsOverlay(false, false);
            mControlVisibleBeforeOnCreateView = true;
        }
    }
    @Override
    public void onResume() {
        super.onResume();
        if (mControlVisible) {
            //StateGraph: 6->5 1->2
            if (mFadingEnabled) {
                // StateGraph 1->2
                startFadeTimer(mAutohideTimerAfterPlayingInMs);
            }
        } else {
            //StateGraph: 6->7 1->3
        }
        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
        if (mHostCallback != null) {
            mHostCallback.onHostResume();
        }
    }
    private void stopFadeTimer() {
        if (mHandler != null) {
            mHandler.removeMessages(START_FADE_OUT);
        }
    }
    private void startFadeTimer(int fadeOutTimeout) {
        if (mHandler != null) {
            mHandler.removeMessages(START_FADE_OUT);
            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, fadeOutTimeout);
        }
    }
    private static ValueAnimator loadAnimator(Context context, int resId) {
        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
        return animator;
    }
    private void loadBgAnimator() {
        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator arg0) {
                setBgAlpha((Integer) arg0.getAnimatedValue());
            }
        };
        Context context = getContext();
        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
        mBgFadeInAnimator.addUpdateListener(listener);
        mBgFadeInAnimator.addListener(mFadeListener);
        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
        mBgFadeOutAnimator.addUpdateListener(listener);
        mBgFadeOutAnimator.addListener(mFadeListener);
    }
    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0);
    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100, 0);
    private void loadControlRowAnimator() {
        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator arg0) {
                if (getVerticalGridView() == null) {
                    return;
                }
                RecyclerView.ViewHolder vh = getVerticalGridView()
                        .findViewHolderForAdapterPosition(0);
                if (vh == null) {
                    return;
                }
                View view = vh.itemView;
                if (view != null) {
                    final float fraction = (Float) arg0.getAnimatedValue();
                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
                    view.setAlpha(fraction);
                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
                }
            }
        };
        Context context = getContext();
        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
        mControlRowFadeInAnimator.addUpdateListener(updateListener);
        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
        mControlRowFadeOutAnimator = loadAnimator(context,
                R.animator.lb_playback_controls_fade_out);
        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
    }
    private void loadOtherRowAnimator() {
        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator arg0) {
                if (getVerticalGridView() == null) {
                    return;
                }
                final float fraction = (Float) arg0.getAnimatedValue();
                final int count = getVerticalGridView().getChildCount();
                for (int i = 0; i < count; i++) {
                    View view = getVerticalGridView().getChildAt(i);
                    if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
                        view.setAlpha(fraction);
                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
                    }
                }
            }
        };
        Context context = getContext();
        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
    }
    /**
     * Fades out the playback overlay immediately.
     * @deprecated Call {@link #hideControlsOverlay(boolean)}
     */
    @Deprecated
    public void fadeOut() {
        showControlsOverlay(false, false);
    }
    /**
     * Show controls overlay.
     *
     * @param runAnimation True to run animation, false otherwise.
     */
    public void showControlsOverlay(boolean runAnimation) {
        showControlsOverlay(true, runAnimation);
    }
    /**
     * Returns true if controls overlay is visible, false otherwise.
     *
     * @return True if controls overlay is visible, false otherwise.
     * @see #showControlsOverlay(boolean)
     * @see #hideControlsOverlay(boolean)
     */
    public boolean isControlsOverlayVisible() {
        return mControlVisible;
    }
    /**
     * Hide controls overlay.
     *
     * @param runAnimation True to run animation, false otherwise.
     */
    public void hideControlsOverlay(boolean runAnimation) {
        showControlsOverlay(false, runAnimation);
    }
    /**
     * if first animator is still running, reverse it; otherwise start second animator.
     */
    static void reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second,
            boolean runAnimation) {
        if (first.isStarted()) {
            first.reverse();
            if (!runAnimation) {
                first.end();
            }
        } else {
            second.start();
            if (!runAnimation) {
                second.end();
            }
        }
    }
    /**
     * End first or second animator if they are still running.
     */
    static void endAll(ValueAnimator first, ValueAnimator second) {
        if (first.isStarted()) {
            first.end();
        } else if (second.isStarted()) {
            second.end();
        }
    }
    /**
     * Fade in or fade out rows and background.
     *
     * @param show True to fade in, false to fade out.
     * @param animation True to run animation.
     */
    void showControlsOverlay(boolean show, boolean animation) {
        if (DEBUG) Log.v(TAG, "showControlsOverlay " + show);
        if (getView() == null) {
            mControlVisibleBeforeOnCreateView = show;
            return;
        }
        // force no animation when fragment is not resumed
        if (!isResumed()) {
            animation = false;
        }
        if (show == mControlVisible) {
            if (!animation) {
                // End animation if needed
                endAll(mBgFadeInAnimator, mBgFadeOutAnimator);
                endAll(mControlRowFadeInAnimator, mControlRowFadeOutAnimator);
                endAll(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator);
            }
            return;
        }
        // StateGraph: 7<->5 4<->3 2->3
        mControlVisible = show;
        if (!mControlVisible) {
            // StateGraph 2->3
            stopFadeTimer();
        }
        mAnimationTranslateY = (getVerticalGridView() == null
                || getVerticalGridView().getSelectedPosition() == 0)
                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
        if (show) {
            reverseFirstOrStartSecond(mBgFadeOutAnimator, mBgFadeInAnimator, animation);
            reverseFirstOrStartSecond(mControlRowFadeOutAnimator, mControlRowFadeInAnimator,
                    animation);
            reverseFirstOrStartSecond(mOtherRowFadeOutAnimator, mOtherRowFadeInAnimator, animation);
        } else {
            reverseFirstOrStartSecond(mBgFadeInAnimator, mBgFadeOutAnimator, animation);
            reverseFirstOrStartSecond(mControlRowFadeInAnimator, mControlRowFadeOutAnimator,
                    animation);
            reverseFirstOrStartSecond(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator, animation);
        }
        if (animation) {
            getView().announceForAccessibility(getString(show
                    ? R.string.lb_playback_controls_shown
                    : R.string.lb_playback_controls_hidden));
        }
    }
    /**
     * 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);
        }
    }
    private void setupChildFragmentLayout() {
        setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
    }
    void setVerticalGridViewLayout(VerticalGridView listview) {
        if (listview == null) {
            return;
        }
        // we set the base line of alignment to -paddingBottom
        listview.setWindowAlignmentOffset(-mPaddingBottom);
        listview.setWindowAlignmentOffsetPercent(
                VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
        // align other rows that arent the last to center of screen, since our baseline is
        // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
        listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
        listview.setItemAlignmentOffsetPercent(50);
        // Push last row to the bottom padding
        // Padding affects alignment when last row is focused
        listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
                listview.getPaddingRight(), mPaddingBottom);
        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mOtherRowsCenterToBottom = getResources()
                .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
        mPaddingBottom =
                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
        mBgDarkColor =
                getResources().getColor(R.color.lb_playback_controls_background_dark);
        mBgLightColor =
                getResources().getColor(R.color.lb_playback_controls_background_light);
        TypedValue outValue = new TypedValue();
        getContext().getTheme().resolveAttribute(
                R.attr.playbackControlsAutoHideTimeout, outValue, true);
        mAutohideTimerAfterPlayingInMs = outValue.data;
        getContext().getTheme().resolveAttribute(
                R.attr.playbackControlsAutoHideTickleTimeout, outValue, true);
        mAutohideTimerAfterTickleInMs = outValue.data;
        mMajorFadeTranslateY =
                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
        mMinorFadeTranslateY =
                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
        loadBgAnimator();
        loadControlRowAnimator();
        loadOtherRowAnimator();
    }
    /**
     * Sets the background type.
     *
     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
     */
    public void setBackgroundType(int type) {
        switch (type) {
            case BG_LIGHT:
            case BG_DARK:
            case BG_NONE:
                if (type != mBackgroundType) {
                    mBackgroundType = type;
                    updateBackground();
                }
                break;
            default:
                throw new IllegalArgumentException("Invalid background type");
        }
    }
    /**
     * Returns the background type.
     */
    public int getBackgroundType() {
        return mBackgroundType;
    }
    private void updateBackground() {
        if (mBackgroundView != null) {
            int color = mBgDarkColor;
            switch (mBackgroundType) {
                case BG_DARK:
                    break;
                case BG_LIGHT:
                    color = mBgLightColor;
                    break;
                case BG_NONE:
                    color = Color.TRANSPARENT;
                    break;
            }
            mBackgroundView.setBackground(new ColorDrawable(color));
            setBgAlpha(mBgAlpha);
        }
    }
    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
            new ItemBridgeAdapter.AdapterListener() {
                @Override
                public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
                    if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
                    if (!mControlVisible) {
                        if (DEBUG) Log.v(TAG, "setting alpha to 0");
                        vh.getViewHolder().view.setAlpha(0);
                    }
                }
                @Override
                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
                    Presenter.ViewHolder viewHolder = vh.getViewHolder();
                    if (viewHolder instanceof PlaybackSeekUi) {
                        ((PlaybackSeekUi) viewHolder).setPlaybackSeekUiClient(mChainedClient);
                    }
                }
                @Override
                public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
                    if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
                    // Reset animation state
                    vh.getViewHolder().view.setAlpha(1f);
                    vh.getViewHolder().view.setTranslationY(0);
                    vh.getViewHolder().view.setAlpha(1f);
                }
                @Override
                public void onBind(ItemBridgeAdapter.ViewHolder vh) {
                }
            };
    @Override
    @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(R.layout.lb_playback_fragment, container, false);
        mBackgroundView = mRootView.findViewById(R.id.playback_fragment_background);
        mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
                R.id.playback_controls_dock);
        if (mRowsSupportFragment == null) {
            mRowsSupportFragment = new RowsSupportFragment();
            getChildFragmentManager().beginTransaction()
                    .replace(R.id.playback_controls_dock, mRowsSupportFragment)
                    .commit();
        }
        if (mAdapter == null) {
            setAdapter(new ArrayObjectAdapter(new ClassPresenterSelector()));
        } else {
            mRowsSupportFragment.setAdapter(mAdapter);
        }
        mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
        mBgAlpha = 255;
        updateBackground();
        mRowsSupportFragment.setExternalAdapterListener(mAdapterListener);
        ProgressBarManager progressBarManager = getProgressBarManager();
        if (progressBarManager != null) {
            progressBarManager.setRootView((ViewGroup) mRootView);
        }
        return mRootView;
    }
    /**
     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
     * take appropriate actions to take action when the hosting fragment starts/stops processing.
     */
    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
        this.mHostCallback = hostCallback;
    }
    @Override
    public void onStart() {
        super.onStart();
        setupChildFragmentLayout();
        mRowsSupportFragment.setAdapter(mAdapter);
        if (mHostCallback != null) {
            mHostCallback.onHostStart();
        }
    }
    @Override
    public void onStop() {
        if (mHostCallback != null) {
            mHostCallback.onHostStop();
        }
        super.onStop();
    }
    @Override
    public void onPause() {
        if (mHostCallback != null) {
            mHostCallback.onHostPause();
        }
        if (mHandler.hasMessages(START_FADE_OUT)) {
            // StateGraph: 2->1
            mHandler.removeMessages(START_FADE_OUT);
        } else {
            // StateGraph: 5->6, 7->6, 4->1, 3->1
        }
        super.onPause();
    }
    /**
     * This listener is called every time there is a selection in {@link RowsSupportFragment}. This can
     * be used by users to take additional actions such as animations.
     */
    public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
        mExternalItemSelectedListener = listener;
    }
    /**
     * This listener is called every time there is a click in {@link RowsSupportFragment}. This can
     * be used by users to take additional actions such as animations.
     */
    public void setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
        mExternalItemClickedListener = listener;
    }
    /**
     * Sets the {@link BaseOnItemViewClickedListener} that would be invoked for clicks
     * only on {@link androidx.leanback.widget.PlaybackRowPresenter.ViewHolder}.
     */
    public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
        mPlaybackItemClickedListener = listener;
    }
    @Override
    public void onDestroyView() {
        mRootView = null;
        mBackgroundView = null;
        super.onDestroyView();
    }
    @Override
    public void onDestroy() {
        if (mHostCallback != null) {
            mHostCallback.onHostDestroy();
        }
        super.onDestroy();
    }
    /**
     * Sets the playback row for the playback controls. The row will be set as first element
     * of adapter if the adapter is {@link ArrayObjectAdapter} or {@link SparseArrayObjectAdapter}.
     * @param row The row that represents the playback.
     */
    public void setPlaybackRow(Row row) {
        this.mRow = row;
        setupRow();
        setupPresenter();
    }
    /**
     * Sets the presenter for rendering the playback row set by {@link #setPlaybackRow(Row)}. If
     * adapter does not set a {@link PresenterSelector}, {@link #setAdapter(ObjectAdapter)} will
     * create a {@link ClassPresenterSelector} by default and map from the row object class to this
     * {@link PlaybackRowPresenter}.
     *
     * @param  presenter Presenter used to render {@link #setPlaybackRow(Row)}.
     */
    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
        this.mPresenter = presenter;
        setupPresenter();
        setPlaybackRowPresenterAlignment();
    }
    void setPlaybackRowPresenterAlignment() {
        if (mAdapter != null && mAdapter.getPresenterSelector() != null) {
            Presenter[] presenters = mAdapter.getPresenterSelector().getPresenters();
            if (presenters != null) {
                for (int i = 0; i < presenters.length; i++) {
                    if (presenters[i] instanceof PlaybackRowPresenter
                            && presenters[i].getFacet(ItemAlignmentFacet.class) == null) {
                        ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
                        ItemAlignmentFacet.ItemAlignmentDef def =
                                new ItemAlignmentFacet.ItemAlignmentDef();
                        def.setItemAlignmentOffset(0);
                        def.setItemAlignmentOffsetPercent(100);
                        itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
                                {def});
                        presenters[i].setFacet(ItemAlignmentFacet.class, itemAlignment);
                    }
                }
            }
        }
    }
    /**
     * Updates the ui when the row data changes.
     */
    public void notifyPlaybackRowChanged() {
        if (mAdapter == null) {
            return;
        }
        mAdapter.notifyItemRangeChanged(0, 1);
    }
    /**
     * Sets the list of rows for the fragment. A default {@link ClassPresenterSelector} will be
     * created if {@link ObjectAdapter#getPresenterSelector()} is null. if user provides
     * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)},
     * the row and presenter will be set onto the adapter.
     *
     * @param adapter The adapter that contains related rows and optional playback row.
     */
    public void setAdapter(ObjectAdapter adapter) {
        mAdapter = adapter;
        setupRow();
        setupPresenter();
        setPlaybackRowPresenterAlignment();
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setAdapter(adapter);
        }
    }
    private void setupRow() {
        if (mAdapter instanceof ArrayObjectAdapter && mRow != null) {
            ArrayObjectAdapter adapter = ((ArrayObjectAdapter) mAdapter);
            if (adapter.size() == 0) {
                adapter.add(mRow);
            } else {
                adapter.replace(0, mRow);
            }
        } else if (mAdapter instanceof SparseArrayObjectAdapter && mRow != null) {
            SparseArrayObjectAdapter adapter = ((SparseArrayObjectAdapter) mAdapter);
            adapter.set(0, mRow);
        }
    }
    private void setupPresenter() {
        if (mAdapter != null && mRow != null && mPresenter != null) {
            PresenterSelector selector = mAdapter.getPresenterSelector();
            if (selector == null) {
                selector = new ClassPresenterSelector();
                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
                mAdapter.setPresenterSelector(selector);
            } else if (selector instanceof ClassPresenterSelector) {
                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
            }
        }
    }
    final PlaybackSeekUi.Client mChainedClient = new PlaybackSeekUi.Client() {
        @Override
        public boolean isSeekEnabled() {
            return mSeekUiClient == null ? false : mSeekUiClient.isSeekEnabled();
        }
        @Override
        public void onSeekStarted() {
            if (mSeekUiClient != null) {
                mSeekUiClient.onSeekStarted();
            }
            setSeekMode(true);
        }
        @Override
        public PlaybackSeekDataProvider getPlaybackSeekDataProvider() {
            return mSeekUiClient == null ? null : mSeekUiClient.getPlaybackSeekDataProvider();
        }
        @Override
        public void onSeekPositionChanged(long pos) {
            if (mSeekUiClient != null) {
                mSeekUiClient.onSeekPositionChanged(pos);
            }
        }
        @Override
        public void onSeekFinished(boolean cancelled) {
            if (mSeekUiClient != null) {
                mSeekUiClient.onSeekFinished(cancelled);
            }
            setSeekMode(false);
        }
    };
    /**
     * Interface to be implemented by UI widget to support PlaybackSeekUi.
     */
    public void setPlaybackSeekUiClient(PlaybackSeekUi.Client client) {
        mSeekUiClient = client;
    }
    /**
     * Show or hide other rows other than PlaybackRow.
     * @param inSeek True to make other rows visible, false to make other rows invisible.
     */
    void setSeekMode(boolean inSeek) {
        if (mInSeek == inSeek) {
            return;
        }
        mInSeek = inSeek;
        getVerticalGridView().setSelectedPosition(0);
        if (mInSeek) {
            stopFadeTimer();
        }
        // immediately fade in control row.
        showControlsOverlay(true);
        final int count = getVerticalGridView().getChildCount();
        for (int i = 0; i < count; i++) {
            View view = getVerticalGridView().getChildAt(i);
            if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
                view.setVisibility(mInSeek ? View.INVISIBLE : View.VISIBLE);
            }
        }
    }
    /**
     * Called when size of the video changes. App may override.
     * @param videoWidth Intrinsic width of video
     * @param videoHeight Intrinsic height of video
     */
    protected void onVideoSizeChanged(int videoWidth, int videoHeight) {
    }
    /**
     * Called when media has start or stop buffering. App may override. The default initial state
     * is not buffering.
     * @param start True for buffering start, false otherwise.
     */
    protected void onBufferingStateChanged(boolean start) {
        ProgressBarManager progressBarManager = getProgressBarManager();
        if (progressBarManager != null) {
            if (start) {
                progressBarManager.show();
            } else {
                progressBarManager.hide();
            }
        }
    }
    /**
     * Called when media has error. App may override.
     * @param errorCode Optional error code for specific implementation.
     * @param errorMessage Optional error message for specific implementation.
     */
    protected void onError(int errorCode, CharSequence errorMessage) {
    }
    /**
     * Returns the ProgressBarManager that will show or hide progress bar in
     * {@link #onBufferingStateChanged(boolean)}.
     * @return The ProgressBarManager that will show or hide progress bar in
     * {@link #onBufferingStateChanged(boolean)}.
     */
    public ProgressBarManager getProgressBarManager() {
        return mProgressBarManager;
    }
}