1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.os.Bundle;
17import android.support.v17.leanback.R;
18import android.support.v17.leanback.transition.TransitionHelper;
19import android.support.v17.leanback.transition.TransitionListener;
20import android.view.View;
21import android.view.ViewTreeObserver;
22
23import android.support.v17.leanback.util.StateMachine;
24import android.support.v17.leanback.util.StateMachine.State;
25
26import static android.support.v17.leanback.util.StateMachine.*;
27
28/**
29 * @hide
30 */
31class BaseFragment extends BrandedFragment {
32
33    /**
34     * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true
35     * Action: none
36     */
37    private final State STATE_ALLOWED = new State() {
38        @Override
39        public boolean canRun() {
40            return TransitionHelper.systemSupportsEntranceTransitions();
41        }
42
43        @Override
44        public void run() {
45            mProgressBarManager.show();
46        }
47    };
48
49    /**
50     * Condition: {@link #isReadyForPrepareEntranceTransition()} is true
51     * Action: {@link #onEntranceTransitionPrepare()} }
52     */
53    private final State STATE_PREPARE = new State() {
54        @Override
55        public boolean canRun() {
56            return isReadyForPrepareEntranceTransition();
57        }
58
59        @Override
60        public void run() {
61            onEntranceTransitionPrepare();
62        }
63    };
64
65    /**
66     * Condition: {@link #isReadyForStartEntranceTransition()} is true
67     * Action: {@link #onExecuteEntranceTransition()} }
68     */
69    private final State STATE_START = new State() {
70        @Override
71        public boolean canRun() {
72            return isReadyForStartEntranceTransition();
73        }
74
75        @Override
76        public void run() {
77            mProgressBarManager.hide();
78            onExecuteEntranceTransition();
79        }
80    };
81
82    final StateMachine mEnterTransitionStates;
83
84    private Object mEntranceTransition;
85    private final ProgressBarManager mProgressBarManager = new ProgressBarManager();
86
87    BaseFragment() {
88        mEnterTransitionStates = new StateMachine();
89        mEnterTransitionStates.addState(STATE_ALLOWED);
90        mEnterTransitionStates.addState(STATE_PREPARE);
91        mEnterTransitionStates.addState(STATE_START);
92    }
93
94    @Override
95    public void onViewCreated(View view, Bundle savedInstanceState) {
96        super.onViewCreated(view, savedInstanceState);
97        performPendingStates();
98    }
99
100    final void performPendingStates() {
101        mEnterTransitionStates.runPendingStates();
102    }
103
104    /**
105     * Enables entrance transition.<p>
106     * Entrance transition is the standard slide-in transition that shows rows of data in
107     * browse screen and details screen.
108     * <p>
109     * The method is ignored before LOLLIPOP (API21).
110     * <p>
111     * This method must be called in or
112     * before onCreate().  Typically entrance transition should be enabled when savedInstance is
113     * null so that fragment restored from instanceState does not run an extra entrance transition.
114     * When the entrance transition is enabled, the fragment will make headers and content
115     * hidden initially.
116     * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
117     * the transition, otherwise the rows will be invisible forever.
118     * <p>
119     * It is similar to android:windowsEnterTransition and can be considered a late-executed
120     * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
121     * <li> Workaround the problem that activity transition is not available between launcher and
122     * app.  Browse activity must programmatically start the slide-in transition.</li>
123     * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
124     * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
125     * to be loaded.</li>
126     * <p>
127     * Transition object is returned by createEntranceTransition().  Typically the app does not need
128     * override the default transition that browse and details provides.
129     */
130    public void prepareEntranceTransition() {
131        mEnterTransitionStates.runState(STATE_ALLOWED);
132        mEnterTransitionStates.runState(STATE_PREPARE);
133    }
134
135    /**
136     * Return true if entrance transition is enabled and not started yet.
137     * Entrance transition can only be executed once and isEntranceTransitionEnabled()
138     * is reset to false after entrance transition is started.
139     */
140    boolean isEntranceTransitionEnabled() {
141        // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call.
142        return STATE_ALLOWED.getStatus() == STATUS_EXECUTED;
143    }
144
145    /**
146     * Create entrance transition.  Subclass can override to load transition from
147     * resource or construct manually.  Typically app does not need to
148     * override the default transition that browse and details provides.
149     */
150    protected Object createEntranceTransition() {
151        return null;
152    }
153
154    /**
155     * Run entrance transition.  Subclass may use TransitionManager to perform
156     * go(Scene) or beginDelayedTransition().  App should not override the default
157     * implementation of browse and details fragment.
158     */
159    protected void runEntranceTransition(Object entranceTransition) {
160    }
161
162    /**
163     * Callback when entrance transition is prepared.  This is when fragment should
164     * stop user input and animations.
165     */
166    protected void onEntranceTransitionPrepare() {
167    }
168
169    /**
170     * Callback when entrance transition is started.  This is when fragment should
171     * stop processing layout.
172     */
173    protected void onEntranceTransitionStart() {
174    }
175
176    /**
177     * Callback when entrance transition is ended.
178     */
179    protected void onEntranceTransitionEnd() {
180    }
181
182    /**
183     * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
184     * Subclass may override and add additional conditions.
185     * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
186     * Subclass may override and add additional conditions.
187     */
188    boolean isReadyForPrepareEntranceTransition() {
189        return getView() != null;
190    }
191
192    /**
193     * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
194     * Subclass may override and add additional conditions.
195     * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
196     * Subclass may override and add additional conditions.
197     */
198    boolean isReadyForStartEntranceTransition() {
199        return getView() != null;
200    }
201
202    /**
203     * When fragment finishes loading data, it should call startEntranceTransition()
204     * to execute the entrance transition.
205     * startEntranceTransition() will start transition only if both two conditions
206     * are satisfied:
207     * <li> prepareEntranceTransition() was called.</li>
208     * <li> has not executed entrance transition yet.</li>
209     * <p>
210     * If startEntranceTransition() is called before onViewCreated(), it will be pending
211     * and executed when view is created.
212     */
213    public void startEntranceTransition() {
214        mEnterTransitionStates.runState(STATE_START);
215    }
216
217    void onExecuteEntranceTransition() {
218        // wait till views get their initial position before start transition
219        final View view = getView();
220        view.getViewTreeObserver().addOnPreDrawListener(
221                new ViewTreeObserver.OnPreDrawListener() {
222            @Override
223            public boolean onPreDraw() {
224                view.getViewTreeObserver().removeOnPreDrawListener(this);
225                internalCreateEntranceTransition();
226                if (mEntranceTransition != null) {
227                    onEntranceTransitionStart();
228                    runEntranceTransition(mEntranceTransition);
229                }
230                return false;
231            }
232        });
233        view.invalidate();
234    }
235
236    void internalCreateEntranceTransition() {
237        mEntranceTransition = createEntranceTransition();
238        if (mEntranceTransition == null) {
239            return;
240        }
241        TransitionHelper.addTransitionListener(mEntranceTransition, new TransitionListener() {
242            @Override
243            public void onTransitionEnd(Object transition) {
244                mEntranceTransition = null;
245                onEntranceTransitionEnd();
246                mEnterTransitionStates.resetStatus();
247            }
248        });
249    }
250
251    /**
252     * Returns the {@link ProgressBarManager}.
253     */
254    public final ProgressBarManager getProgressBarManager() {
255        return mProgressBarManager;
256    }
257}
258