/*
* 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 android.support.v17.leanback.app;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.transition.TransitionListener;
import android.support.v17.leanback.util.StateMachine.Event;
import android.support.v17.leanback.util.StateMachine.State;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.BrowseFrameLayout;
import android.support.v17.leanback.widget.DetailsParallax;
import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.ItemAlignmentFacet;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.VerticalGridView;
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 java.lang.ref.WeakReference;
/**
* A fragment for creating Leanback details screens.
*
*
* A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set
* of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
*
*
* When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter, DetailsFragment will
* setup default behavior of the DetailsOverviewRow:
*
* The alignment of FullWidthDetailsOverviewRowPresenter is setup in
* {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
*
*
* The view status switching of FullWidthDetailsOverviewRowPresenter is done in
* {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
* FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
*
*
*
* The recommended activity themes to use with a DetailsFragment are
*
* {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity
* shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
*
*
* {@link android.support.v17.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}.
*
*
*
*
* DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
* background and embedded video playing fragment.
*
*/
public class DetailsFragment extends BaseFragment {
static final String TAG = "DetailsFragment";
static boolean DEBUG = false;
final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
@Override
public void run() {
mRowsFragment.setEntranceTransitionState(false);
}
};
final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
void switchToVideoBeforeVideoFragmentCreated() {
// if the video fragment is not ready: immediately fade out covering drawable,
// hide title and mark mPendingFocusOnVideo and set focus on it later.
mDetailsBackgroundController.switchToVideoBeforeCreate();
showTitle(false);
mPendingFocusOnVideo = true;
slideOutGridView();
}
final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
false, false) {
@Override
public void run() {
switchToVideoBeforeVideoFragmentCreated();
}
};
final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
false, false) {
@Override
public void run() {
if (mWaitEnterTransitionTimeout != null) {
mWaitEnterTransitionTimeout.mRef.clear();
}
// clear the activity enter/sharedElement transition, return transitions are kept.
// keep the return transitions and clear enter transition
if (getActivity() != null) {
Window window = getActivity().getWindow();
Object returnTransition = TransitionHelper.getReturnTransition(window);
Object sharedReturnTransition = TransitionHelper
.getSharedElementReturnTransition(window);
TransitionHelper.setEnterTransition(window, null);
TransitionHelper.setSharedElementEnterTransition(window, null);
TransitionHelper.setReturnTransition(window, returnTransition);
TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
}
}
};
final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
true, false);
final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
@Override
public void run() {
Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
}
};
final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
@Override
public void run() {
if (mWaitEnterTransitionTimeout == null) {
new WaitEnterTransitionTimeout(DetailsFragment.this);
}
}
};
/**
* Start this task when first DetailsOverviewRow is created, if there is no entrance transition
* started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
*/
static class WaitEnterTransitionTimeout implements Runnable {
static final long WAIT_ENTERTRANSITION_START = 200;
final WeakReference mRef;
WaitEnterTransitionTimeout(DetailsFragment f) {
mRef = new WeakReference<>(f);
f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
}
@Override
public void run() {
DetailsFragment f = mRef.get();
if (f != null) {
f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
}
}
}
final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
@Override
public void run() {
onSafeStart();
}
};
final Event EVT_ONSTART = new Event("onStart");
final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
@Override
void createStateMachineStates() {
super.createStateMachineStates();
mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
mStateMachine.addState(STATE_ON_SAFE_START);
mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
}
@Override
void createStateMachineTransitions() {
super.createStateMachineTransitions();
/**
* Part 1: Processing enter transitions after fragment.onCreate
*/
mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
// if transition is not supported, skip to complete
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
COND_TRANSITION_NOT_SUPPORTED);
// if transition is not set on Activity, skip to complete
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
EVT_NO_ENTER_TRANSITION);
// if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
// complete.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
EVT_SWITCH_TO_VIDEO);
mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
// once after onCreateView, we cannot skip the enter transition, add a listener and wait
// it to finish
mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
EVT_ON_CREATEVIEW);
// when enter transition finishes, go to complete, however this might never happen if
// the activity is not giving transition options in startActivity, there is no API to query
// if this activity is started in a enter transition mode. So we rely on a timer below:
mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
// we are expecting app to start delayed enter transition shortly after details row is
// loaded, so create a timer and wait for enter transition start.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
// if enter transition not started in the timer, skip to DONE, this can be also true when
// startActivity is not giving transition option.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
EVT_ENTER_TRANSIITON_DONE);
/**
* Part 2: modification to the entrance transition defined in BaseFragment
*/
// Must finish enter transition before perform entrance transition.
mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
// Calling switch to video would hide immediately and skip entrance transition
mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
EVT_SWITCH_TO_VIDEO);
mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
// if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
// still need to do the switchToVideo.
mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
EVT_SWITCH_TO_VIDEO);
// for once the view is created in onStart and prepareEntranceTransition was called, we
// could setEntranceStartState:
mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
/**
* Part 3: onSafeStart()
*/
// for onSafeStart: the condition is onStart called, entrance transition complete
mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
}
private class SetSelectionRunnable implements Runnable {
int mPosition;
boolean mSmooth = true;
SetSelectionRunnable() {
}
@Override
public void run() {
if (mRowsFragment == null) {
return;
}
mRowsFragment.setSelectedPosition(mPosition, mSmooth);
}
}
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 mVideoFragment;
DetailsParallax mDetailsParallax;
RowsFragment mRowsFragment;
ObjectAdapter mAdapter;
int mContainerListAlignTop;
BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
BaseOnItemViewClickedListener mOnItemViewClickedListener;
DetailsFragmentBackgroundController mDetailsBackgroundController;
// A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
// true, we will focus to VideoFragment immediately after video fragment's view is created.
boolean mPendingFocusOnVideo = false;
WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
Object mSceneAfterEntranceTransition;
final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
final BaseOnItemViewSelectedListener