blob: 3fb1149674cde1f28046a575abd10d5c24aed866 [file] [log] [blame]
// CHECKSTYLE:OFF Generated code
/* This file is auto-generated from DetailsFragment.java. DO NOT MODIFY. */
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package androidx.leanback.app;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.CallSuper;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import androidx.leanback.R;
import androidx.leanback.transition.TransitionHelper;
import androidx.leanback.transition.TransitionListener;
import androidx.leanback.util.StateMachine.Event;
import androidx.leanback.util.StateMachine.State;
import androidx.leanback.widget.BaseOnItemViewClickedListener;
import androidx.leanback.widget.BaseOnItemViewSelectedListener;
import androidx.leanback.widget.BrowseFrameLayout;
import androidx.leanback.widget.DetailsParallax;
import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
import androidx.leanback.widget.ItemAlignmentFacet;
import androidx.leanback.widget.ItemBridgeAdapter;
import androidx.leanback.widget.ObjectAdapter;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.PresenterSelector;
import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.VerticalGridView;
import java.lang.ref.WeakReference;
/**
* A fragment for creating Leanback details screens.
*
* <p>
* A DetailsSupportFragment renders the elements of its {@link ObjectAdapter} as a set
* of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
*
* When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter, DetailsSupportFragment will
* setup default behavior of the DetailsOverviewRow:
* <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>
*
* <p>
* The recommended activity themes to use with a DetailsSupportFragment are
* <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>
* </p>
*
* <p>
* DetailsSupportFragment can use {@link DetailsSupportFragmentBackgroundController} to add a parallax drawable
* background and embedded video playing fragment.
* </p>
*/
public class DetailsSupportFragment extends BaseSupportFragment {
static final String TAG = "DetailsSupportFragment";
static final boolean DEBUG = false;
final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
@Override
public void run() {
mRowsSupportFragment.setEntranceTransitionState(false);
}
};
final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
void switchToVideoBeforeVideoSupportFragmentCreated() {
// if the video fragment is not ready: immediately fade out covering drawable,
// hide title and mark mPendingFocusOnVideo and set focus on it later.
mDetailsBackgroundController.switchToVideoBeforeCreate();
showTitle(false);
mPendingFocusOnVideo = true;
slideOutGridView();
}
final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
false, false) {
@Override
public void run() {
switchToVideoBeforeVideoSupportFragmentCreated();
}
};
final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
false, false) {
@Override
public void run() {
if (mWaitEnterTransitionTimeout != null) {
mWaitEnterTransitionTimeout.mRef.clear();
}
// clear the activity enter/sharedElement transition, return transitions are kept.
// keep the return transitions and clear enter transition
if (getActivity() != null) {
Window window = getActivity().getWindow();
Object returnTransition = TransitionHelper.getReturnTransition(window);
Object sharedReturnTransition = TransitionHelper
.getSharedElementReturnTransition(window);
TransitionHelper.setEnterTransition(window, null);
TransitionHelper.setSharedElementEnterTransition(window, null);
TransitionHelper.setReturnTransition(window, returnTransition);
TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
}
}
};
final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
true, false);
final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
@Override
public void run() {
Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
}
};
final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
@Override
public void run() {
if (mWaitEnterTransitionTimeout == null) {
new WaitEnterTransitionTimeout(DetailsSupportFragment.this);
}
}
};
/**
* Start this task when first DetailsOverviewRow is created, if there is no entrance transition
* started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
*/
static class WaitEnterTransitionTimeout implements Runnable {
static final long WAIT_ENTERTRANSITION_START = 200;
final WeakReference<DetailsSupportFragment> mRef;
WaitEnterTransitionTimeout(DetailsSupportFragment f) {
mRef = new WeakReference<>(f);
f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
}
@Override
public void run() {
DetailsSupportFragment f = mRef.get();
if (f != null) {
f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
}
}
}
final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
@Override
public void run() {
onSafeStart();
}
};
final Event EVT_ONSTART = new Event("onStart");
final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
@Override
void createStateMachineStates() {
super.createStateMachineStates();
mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
mStateMachine.addState(STATE_ON_SAFE_START);
mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
}
@Override
void createStateMachineTransitions() {
super.createStateMachineTransitions();
/**
* Part 1: Processing enter transitions after fragment.onCreate
*/
mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
// if transition is not supported, skip to complete
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
COND_TRANSITION_NOT_SUPPORTED);
// if transition is not set on Activity, skip to complete
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
EVT_NO_ENTER_TRANSITION);
// if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
// complete.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
EVT_SWITCH_TO_VIDEO);
mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
// once after onCreateView, we cannot skip the enter transition, add a listener and wait
// it to finish
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
EVT_ON_CREATEVIEW);
// when enter transition finishes, go to complete, however this might never happen if
// the activity is not giving transition options in startActivity, there is no API to query
// if this activity is started in a enter transition mode. So we rely on a timer below:
mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
// we are expecting app to start delayed enter transition shortly after details row is
// loaded, so create a timer and wait for enter transition start.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
// if enter transition not started in the timer, skip to DONE, this can be also true when
// startActivity is not giving transition option.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
EVT_ENTER_TRANSIITON_DONE);
/**
* Part 2: modification to the entrance transition defined in BaseSupportFragment
*/
// Must finish enter transition before perform entrance transition.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
// Calling switch to video would hide immediately and skip entrance transition
mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
EVT_SWITCH_TO_VIDEO);
mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
// if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
// still need to do the switchToVideo.
mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
EVT_SWITCH_TO_VIDEO);
// for once the view is created in onStart and prepareEntranceTransition was called, we
// could setEntranceStartState:
mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
/**
* Part 3: onSafeStart()
*/
// for onSafeStart: the condition is onStart called, entrance transition complete
mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
}
private class SetSelectionRunnable implements Runnable {
int mPosition;
boolean mSmooth = true;
SetSelectionRunnable() {
}
@Override
public void run() {
if (mRowsSupportFragment == null) {
return;
}
mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
}
}
TransitionListener mEnterTransitionListener = new TransitionListener() {
@Override
public void onTransitionStart(Object transition) {
if (mWaitEnterTransitionTimeout != null) {
// cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
// when transition finishes.
mWaitEnterTransitionTimeout.mRef.clear();
}
}
@Override
public void onTransitionCancel(Object transition) {
mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
}
@Override
public void onTransitionEnd(Object transition) {
mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
}
};
TransitionListener mReturnTransitionListener = new TransitionListener() {
@Override
public void onTransitionStart(Object transition) {
onReturnTransitionStart();
}
};
BrowseFrameLayout mRootView;
View mBackgroundView;
Drawable mBackgroundDrawable;
Fragment mVideoSupportFragment;
DetailsParallax mDetailsParallax;
RowsSupportFragment mRowsSupportFragment;
ObjectAdapter mAdapter;
int mContainerListAlignTop;
BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
BaseOnItemViewClickedListener mOnItemViewClickedListener;
DetailsSupportFragmentBackgroundController mDetailsBackgroundController;
// A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
// true, we will focus to VideoSupportFragment immediately after video fragment's view is created.
boolean mPendingFocusOnVideo = false;
WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
Object mSceneAfterEntranceTransition;
final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
new BaseOnItemViewSelectedListener<Object>() {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Object row) {
int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
if (DEBUG) Log.v(TAG, "row selected position " + position
+ " subposition " + subposition);
onRowSelected(position, subposition);
if (mExternalOnItemViewSelectedListener != null) {
mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
rowViewHolder, row);
}
}
};
/**
* Sets the list of rows for the fragment.
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
if (presenters != null) {
for (int i = 0; i < presenters.length; i++) {
setupPresenter(presenters[i]);
}
} else {
Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
}
if (mRowsSupportFragment != null) {
mRowsSupportFragment.setAdapter(adapter);
}
}
/**
* Returns the list of rows.
*/
public ObjectAdapter getAdapter() {
return mAdapter;
}
/**
* Sets an item selection listener.
*/
public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mExternalOnItemViewSelectedListener = listener;
}
/**
* Sets an item clicked listener.
*/
public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
if (mOnItemViewClickedListener != listener) {
mOnItemViewClickedListener = listener;
if (mRowsSupportFragment != null) {
mRowsSupportFragment.setOnItemViewClickedListener(listener);
}
}
}
/**
* Returns the item clicked listener.
*/
public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContainerListAlignTop =
getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
FragmentActivity activity = getActivity();
if (activity != null) {
Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
if (transition == null) {
mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
}
transition = TransitionHelper.getReturnTransition(activity.getWindow());
if (transition != null) {
TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
}
} else {
mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
}
}
@Override
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);
}
mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
R.id.details_rows_dock);
if (mRowsSupportFragment == null) {
mRowsSupportFragment = new RowsSupportFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
}
installTitleView(inflater, mRootView, savedInstanceState);
mRowsSupportFragment.setAdapter(mAdapter);
mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
@Override
public void run() {
mRowsSupportFragment.setEntranceTransitionState(true);
}
});
setupDpadNavigation();
if (Build.VERSION.SDK_INT >= 21) {
// Setup adapter listener to work with ParallaxTransition (>= API 21).
mRowsSupportFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
if (mDetailsParallax != null && vh.getViewHolder()
instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
(FullWidthDetailsOverviewRowPresenter.ViewHolder)
vh.getViewHolder();
rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
mDetailsParallax);
}
}
});
}
return mRootView;
}
/**
* @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 DetailsSupportFragment.
* For example how a row is aligned in details Fragment. The default implementation invokes
* {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
*
*/
protected void setupPresenter(Presenter rowPresenter) {
if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
}
}
/**
* Called to setup {@link FullWidthDetailsOverviewRowPresenter}. The default implementation
* adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
* FullWidthDetailsOverviewRowPresenter to align in fragment.
*/
protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
ItemAlignmentFacet facet = new ItemAlignmentFacet();
// by default align details_frame to half window height
ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
alignDef1.setItemAlignmentViewId(R.id.details_frame);
alignDef1.setItemAlignmentOffset(- getResources()
.getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
alignDef1.setItemAlignmentOffsetPercent(0);
// when description is selected, align details_frame to top edge
ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
alignDef2.setItemAlignmentViewId(R.id.details_frame);
alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
alignDef2.setItemAlignmentOffset(- getResources()
.getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
alignDef2.setItemAlignmentOffsetPercent(0);
ItemAlignmentFacet.ItemAlignmentDef[] defs =
new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
facet.setAlignmentDefs(defs);
presenter.setFacet(ItemAlignmentFacet.class, facet);
}
VerticalGridView getVerticalGridView() {
return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
}
/**
* Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment. If view of
* DetailsSupportFragment is not created, the method returns null.
* @return Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.
*/
public RowsSupportFragment getRowsSupportFragment() {
return mRowsSupportFragment;
}
/**
* Setup dimensions that are only meaningful when the child Fragments are inside
* DetailsSupportFragment.
*/
private void setupChildFragmentLayout() {
setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
}
/**
* Sets the selected row position with smooth animation.
*/
public void setSelectedPosition(int position) {
setSelectedPosition(position, true);
}
/**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
mSetSelectionRunnable.mPosition = position;
mSetSelectionRunnable.mSmooth = smooth;
if (getView() != null && getView().getHandler() != null) {
getView().getHandler().post(mSetSelectionRunnable);
}
}
void switchToVideo() {
if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
mVideoSupportFragment.getView().requestFocus();
} else {
mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
}
}
void switchToRows() {
mPendingFocusOnVideo = false;
VerticalGridView verticalGridView = getVerticalGridView();
if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
verticalGridView.requestFocus();
}
}
/**
* This method asks DetailsSupportFragmentBackgroundController to add a fragment for rendering video.
* In case the fragment is already there, it will return the existing one. The method must be
* called after calling super.onCreate(). App usually does not call this method directly.
*
* @return Fragment the added or restored fragment responsible for rendering video.
* @see DetailsSupportFragmentBackgroundController#onCreateVideoSupportFragment()
*/
final Fragment findOrCreateVideoSupportFragment() {
if (mVideoSupportFragment != null) {
return mVideoSupportFragment;
}
Fragment fragment = getChildFragmentManager()
.findFragmentById(R.id.video_surface_container);
if (fragment == null && mDetailsBackgroundController != null) {
FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
ft2.add(androidx.leanback.R.id.video_surface_container,
fragment = mDetailsBackgroundController.onCreateVideoSupportFragment());
ft2.commit();
if (mPendingFocusOnVideo) {
// wait next cycle for Fragment view created so we can focus on it.
// This is a bit hack eventually we will do commitNow() which get view immediately.
getView().post(new Runnable() {
@Override
public void run() {
if (getView() != null) {
switchToVideo();
}
mPendingFocusOnVideo = false;
}
});
}
}
mVideoSupportFragment = fragment;
return mVideoSupportFragment;
}
void onRowSelected(int selectedPosition, int selectedSubPosition) {
ObjectAdapter adapter = getAdapter();
if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
&& mRowsSupportFragment.getView().hasFocus() && !mPendingFocusOnVideo)
&& (adapter == null || adapter.size() == 0
|| (getVerticalGridView().getSelectedPosition() == 0
&& getVerticalGridView().getSelectedSubPosition() == 0))) {
showTitle(true);
} else {
showTitle(false);
}
if (adapter != null && adapter.size() > selectedPosition) {
final VerticalGridView gridView = getVerticalGridView();
final int count = gridView.getChildCount();
if (count > 0) {
mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
}
for (int i = 0; i < count; i++) {
ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
gridView.getChildViewHolder(gridView.getChildAt(i));
RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
onSetRowStatus(rowPresenter,
rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
bridgeViewHolder.getAdapterPosition(),
selectedPosition, selectedSubPosition);
}
}
}
/**
* Called when onStart and enter transition (postponed/none postponed) and entrance transition
* are all finished.
*/
@CallSuper
void onSafeStart() {
if (mDetailsBackgroundController != null) {
mDetailsBackgroundController.onStart();
}
}
@CallSuper
void onReturnTransitionStart() {
if (mDetailsBackgroundController != null) {
// first disable parallax effect that auto-start PlaybackGlue.
boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
// if video is not visible we can safely remove VideoSupportFragment,
// otherwise let video playing during return transition.
if (!isVideoVisible && mVideoSupportFragment != null) {
FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
ft2.remove(mVideoSupportFragment);
ft2.commit();
mVideoSupportFragment = null;
}
}
}
@Override
public void onStop() {
if (mDetailsBackgroundController != null) {
mDetailsBackgroundController.onStop();
}
super.onStop();
}
/**
* Called on every visible row to change view status when current selected row position
* or selected sub position changed. Subclass may override. The default
* implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
* FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
* instance of {@link FullWidthDetailsOverviewRowPresenter}.
*
* @param presenter The presenter used to create row ViewHolder.
* @param viewHolder The visible (attached) row ViewHolder, note that it may or may not
* be selected.
* @param adapterPosition The adapter position of viewHolder inside adapter.
* @param selectedPosition The adapter position of currently selected row.
* @param selectedSubPosition The sub position within currently selected row. This is used
* When a row has multiple alignment positions.
*/
protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
adapterPosition, int selectedPosition, int selectedSubPosition) {
if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
(FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
adapterPosition, selectedPosition, selectedSubPosition);
}
}
/**
* Called to change DetailsOverviewRow view status when current selected row position
* or selected sub position changed. Subclass may override. The default
* implementation switches between three states based on the positions:
* {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
* {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
* {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
*
* @param presenter The presenter used to create row ViewHolder.
* @param viewHolder The visible (attached) row ViewHolder, note that it may or may not
* be selected.
* @param adapterPosition The adapter position of viewHolder inside adapter.
* @param selectedPosition The adapter position of currently selected row.
* @param selectedSubPosition The sub position within currently selected row. This is used
* When a row has multiple alignment positions.
*/
protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
int selectedPosition, int selectedSubPosition) {
if (selectedPosition > adapterPosition) {
presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
} else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
} else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
} else {
presenter.setState(viewHolder,
FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
}
}
@Override
public void onStart() {
super.onStart();
setupChildFragmentLayout();
mStateMachine.fireEvent(EVT_ONSTART);
if (mDetailsParallax != null) {
mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
}
if (mPendingFocusOnVideo) {
slideOutGridView();
} else if (!getView().hasFocus()) {
mRowsSupportFragment.getVerticalGridView().requestFocus();
}
}
@Override
protected Object createEntranceTransition() {
return TransitionHelper.loadTransition(getContext(),
R.transition.lb_details_enter_transition);
}
@Override
protected void runEntranceTransition(Object entranceTransition) {
TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
}
@Override
protected void onEntranceTransitionEnd() {
mRowsSupportFragment.onTransitionEnd();
}
@Override
protected void onEntranceTransitionPrepare() {
mRowsSupportFragment.onTransitionPrepare();
}
@Override
protected void onEntranceTransitionStart() {
mRowsSupportFragment.onTransitionStart();
}
/**
* Returns the {@link DetailsParallax} instance used by
* {@link DetailsSupportFragmentBackgroundController} to configure parallax effect of background and
* control embedded video playback. App usually does not use this method directly.
* App may use this method for other custom parallax tasks.
*
* @return The DetailsParallax instance attached to the DetailsSupportFragment.
*/
public DetailsParallax getParallax() {
if (mDetailsParallax == null) {
mDetailsParallax = new DetailsParallax();
if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) {
mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
}
}
return mDetailsParallax;
}
/**
* Set background drawable shown below foreground rows UI and above
* {@link #findOrCreateVideoSupportFragment()}.
*
* @see DetailsSupportFragmentBackgroundController
*/
void setBackgroundDrawable(Drawable drawable) {
if (mBackgroundView != null) {
mBackgroundView.setBackground(drawable);
}
mBackgroundDrawable = drawable;
}
/**
* This method does the following
* <ul>
* <li>sets up focus search handling logic in the root view to enable transitioning between
* half screen/full screen/no video mode.</li>
*
* <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
* transition to appropriate mode like half/full screen video.</li>
* </ul>
*/
void setupDpadNavigation() {
mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
@Override
public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
return false;
}
@Override
public void onRequestChildFocus(View child, View focused) {
if (child != mRootView.getFocusedChild()) {
if (child.getId() == R.id.details_fragment_root) {
if (!mPendingFocusOnVideo) {
slideInGridView();
showTitle(true);
}
} else if (child.getId() == R.id.video_surface_container) {
slideOutGridView();
showTitle(false);
} else {
showTitle(true);
}
}
}
});
mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
@Override
public View onFocusSearch(View focused, int direction) {
if (mRowsSupportFragment.getVerticalGridView() != null
&& mRowsSupportFragment.getVerticalGridView().hasFocus()) {
if (direction == View.FOCUS_UP) {
if (mDetailsBackgroundController != null
&& mDetailsBackgroundController.canNavigateToVideoSupportFragment()
&& mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
return mVideoSupportFragment.getView();
} else if (getTitleView() != null && getTitleView().hasFocusable()) {
return getTitleView();
}
}
} else if (getTitleView() != null && getTitleView().hasFocus()) {
if (direction == View.FOCUS_DOWN) {
if (mRowsSupportFragment.getVerticalGridView() != null) {
return mRowsSupportFragment.getVerticalGridView();
}
}
}
return focused;
}
});
// If we press BACK on remote while in full screen video mode, we should
// transition back to half screen video playback mode.
mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// This is used to check if we are in full screen video mode. This is somewhat
// hacky and relies on the behavior of the video helper class to update the
// focusability of the video surface view.
if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
&& mVideoSupportFragment.getView().hasFocus()) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (getVerticalGridView().getChildCount() > 0) {
getVerticalGridView().requestFocus();
return true;
}
}
}
return false;
}
});
}
/**
* Slides vertical grid view (displaying media item details) out of the screen from below.
*/
void slideOutGridView() {
if (getVerticalGridView() != null) {
getVerticalGridView().animateOut();
}
}
void slideInGridView() {
if (getVerticalGridView() != null) {
getVerticalGridView().animateIn();
}
}
}