GuidedStepSupportFragment.java revision 015eaf265571c84b5d37311f58bc69b2eb4af8d4
1/* This file is auto-generated from GuidedStepFragment.java.  DO NOT MODIFY. */
2
3/*
4 * Copyright (C) 2015 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 * in compliance with the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software distributed under the License
12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.support.v17.leanback.app;
17
18import android.animation.Animator;
19import android.animation.AnimatorSet;
20import android.support.v4.app.FragmentActivity;
21import android.support.v4.app.Fragment;
22import android.support.v4.app.FragmentManager;
23import android.support.v4.app.FragmentManager.BackStackEntry;
24import android.support.v4.app.FragmentTransaction;
25import android.content.Context;
26import android.content.res.TypedArray;
27import android.os.Build;
28import android.os.Bundle;
29import android.support.annotation.NonNull;
30import android.support.v17.leanback.R;
31import android.support.v17.leanback.transition.TransitionHelper;
32import android.support.v17.leanback.widget.GuidanceStylist;
33import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
34import android.support.v17.leanback.widget.GuidedAction;
35import android.support.v17.leanback.widget.GuidedActionAdapter;
36import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
37import android.support.v17.leanback.widget.GuidedActionsStylist;
38import android.support.v17.leanback.widget.VerticalGridView;
39import android.support.v17.leanback.widget.ViewHolderTask;
40import android.support.v4.app.ActivityCompat;
41import android.support.v7.widget.RecyclerView;
42import android.util.AttributeSet;
43import android.util.Log;
44import android.util.TypedValue;
45import android.view.ContextThemeWrapper;
46import android.view.Gravity;
47import android.view.LayoutInflater;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.ViewTreeObserver;
51import android.widget.FrameLayout;
52import android.widget.ImageView;
53import android.widget.LinearLayout;
54import android.widget.TextView;
55
56import java.util.ArrayList;
57import java.util.Collections;
58import java.util.List;
59
60/**
61 * A GuidedStepSupportFragment is used to guide the user through a decision or series of decisions.
62 * It is composed of a guidance view on the left and a view on the right containing a list of
63 * possible actions.
64 * <p>
65 * <h3>Basic Usage</h3>
66 * <p>
67 * Clients of GuidedStepSupportFragment must create a custom subclass to attach to their Activities.
68 * This custom subclass provides the information necessary to construct the user interface and
69 * respond to user actions. At a minimum, subclasses should override:
70 * <ul>
71 * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
72 * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
73 * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
74 * </ul>
75 * <p>
76 * Clients use following helper functions to add GuidedStepSupportFragment to Activity or FragmentManager:
77 * <ul>
78 * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}, to be called during Activity onCreate,
79 * adds GuidedStepSupportFragment as the first Fragment in activity.</li>
80 * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
81 * GuidedStepSupportFragment, int)}, to add GuidedStepSupportFragment on top of existing Fragments or
82 * replacing existing GuidedStepSupportFragment when moving forward to next step.</li>
83 * <li>{@link #finishGuidedStepSupportFragments()} can either finish the activity or pop all
84 * GuidedStepSupportFragment from stack.
85 * <li>If app chooses not to use the helper function, it is the app's responsibility to call
86 * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it
87 * need pops to.
88 * </ul>
89 * <h3>Theming and Stylists</h3>
90 * <p>
91 * GuidedStepSupportFragment delegates its visual styling to classes called stylists. The {@link
92 * GuidanceStylist} is responsible for the left guidance view, while the {@link
93 * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
94 * attributes to derive values associated with the presentation, such as colors, animations, etc.
95 * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
96 * via theming; see their documentation for more information.
97 * <p>
98 * GuidedStepSupportFragments must have access to an appropriate theme in order for the stylists to
99 * function properly.  Specifically, the fragment must receive {@link
100 * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
101 * is set to that theme. Themes can be provided in one of three ways:
102 * <ul>
103 * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
104 * theme that derives from it.</li>
105 * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
106 * existing Activity theme can have an entry added for the attribute {@link
107 * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
108 * this theme will be used by GuidedStepSupportFragment as an overlay to the Activity's theme.</li>
109 * <li>Finally, custom subclasses of GuidedStepSupportFragment may provide a theme through the {@link
110 * #onProvideTheme} method. This can be useful if a subclass is used across multiple
111 * Activities.</li>
112 * </ul>
113 * <p>
114 * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
115 * the Activty's theme.  (Themes whose parent theme is already set to the guided step theme do not
116 * need to set the guidedStepTheme attribute; if set, it will be ignored.)
117 * <p>
118 * If themes do not provide enough customizability, the stylists themselves may be subclassed and
119 * provided to the GuidedStepSupportFragment through the {@link #onCreateGuidanceStylist} and {@link
120 * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
121 * may override layout files; subclasses may also have more complex logic to determine styling.
122 * <p>
123 * <h3>Guided sequences</h3>
124 * <p>
125 * GuidedStepSupportFragments can be grouped together to provide a guided sequence. GuidedStepSupportFragments
126 * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
127 * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
128 * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
129 * custom animations are properly configured. (Custom animations are triggered automatically when
130 * the fragment stack is subsequently popped by any normal mechanism.)
131 * <p>
132 * <i>Note: Currently GuidedStepSupportFragments grouped in this way must all be defined programmatically,
133 * rather than in XML. This restriction may be removed in the future.</i>
134 *
135 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
136 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground
137 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight
138 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels
139 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground
140 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark
141 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation
142 * @see GuidanceStylist
143 * @see GuidanceStylist.Guidance
144 * @see GuidedAction
145 * @see GuidedActionsStylist
146 */
147public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.FocusListener {
148
149    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
150    private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
151
152    private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
153
154    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
155
156    private static final boolean IS_FRAMEWORK_FRAGMENT = false;
157
158    /**
159     * Fragment argument name for UI style.  The argument value is persisted in fragment state and
160     * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
161     * might be changed in one of the three helper functions:
162     * <ul>
163     * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)} sets to
164     * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
165     * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
166     * GuidedStepSupportFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
167     * GuidedStepSupportFragment on stack.</li>
168     * <li>{@link #finishGuidedStepSupportFragments()} changes current GuidedStepSupportFragment to
169     * {@link #UI_STYLE_ENTRANCE} for the non activity case.  This is a special case that changes
170     * the transition settings after fragment has been created,  in order to force current
171     * GuidedStepSupportFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
172     * </ul>
173     * <p>
174     * Argument value can be either:
175     * <ul>
176     * <li>{@link #UI_STYLE_REPLACE}</li>
177     * <li>{@link #UI_STYLE_ENTRANCE}</li>
178     * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li>
179     * </ul>
180     */
181    public static final String EXTRA_UI_STYLE = "uiStyle";
182
183    /**
184     * This is the case that we use GuidedStepSupportFragment to replace another existing
185     * GuidedStepSupportFragment when moving forward to next step. Default behavior of this style is:
186     * <ul>
187     * <li>Enter transition slides in from END(right), exit transition same as
188     * {@link #UI_STYLE_ENTRANCE}.
189     * </li>
190     * </ul>
191     */
192    public static final int UI_STYLE_REPLACE = 0;
193
194    /**
195     * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
196     */
197    @Deprecated
198    public static final int UI_STYLE_DEFAULT = 0;
199
200    /**
201     * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
202     * GuidedStepSupportFragment constructor. This is the case that we show GuidedStepSupportFragment on top of
203     * other content. The default behavior of this style:
204     * <ul>
205     * <li>Enter transition slides in from two sides, exit transition slide out to START(left).
206     * Background will be faded in. Note: Changing exit transition by UI style is not working
207     * because fragment transition asks for exit transition before UI style is restored in Fragment
208     * .onCreate().</li>
209     * </ul>
210     * When popping multiple GuidedStepSupportFragment, {@link #finishGuidedStepSupportFragments()} also changes
211     * the top GuidedStepSupportFragment to UI_STYLE_ENTRANCE in order to run the return transition
212     * (reverse of enter transition) of UI_STYLE_ENTRANCE.
213     */
214    public static final int UI_STYLE_ENTRANCE = 1;
215
216    /**
217     * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first
218     * GuidedStepSupportFragment in a separate activity. The default behavior of this style:
219     * <ul>
220     * <li>Enter transition is assigned null (will rely on activity transition), exit transition is
221     * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working
222     * because fragment transition asks for exit transition before UI style is restored in
223     * Fragment.onCreate().</li>
224     * </ul>
225     */
226    public static final int UI_STYLE_ACTIVITY_ROOT = 2;
227
228    /**
229     * Animation to slide the contents from the side (left/right).
230     * @hide
231     */
232    public static final int SLIDE_FROM_SIDE = 0;
233
234    /**
235     * Animation to slide the contents from the bottom.
236     * @hide
237     */
238    public static final int SLIDE_FROM_BOTTOM = 1;
239
240    private static final String TAG = "GuidedStepSupportFragment";
241    private static final boolean DEBUG = false;
242
243    /**
244     * @hide
245     */
246    public static class DummyFragment extends Fragment {
247        @Override
248        public View onCreateView(LayoutInflater inflater, ViewGroup container,
249                Bundle savedInstanceState) {
250            final View v = new View(inflater.getContext());
251            v.setVisibility(View.GONE);
252            return v;
253        }
254    }
255
256    private int mTheme;
257    private ContextThemeWrapper mThemeWrapper;
258    private GuidanceStylist mGuidanceStylist;
259    private GuidedActionsStylist mActionsStylist;
260    private GuidedActionsStylist mButtonActionsStylist;
261    private GuidedActionAdapter mAdapter;
262    private GuidedActionAdapter mSubAdapter;
263    private GuidedActionAdapter mButtonAdapter;
264    private GuidedActionAdapterGroup mAdapterGroup;
265    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
266    private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
267    private int mSelectedIndex = -1;
268    private int mButtonSelectedIndex = -1;
269    private int entranceTransitionType = SLIDE_FROM_SIDE;
270
271    public GuidedStepSupportFragment() {
272        // We need to supply the theme before any potential call to onInflate in order
273        // for the defaulting to work properly.
274        mTheme = onProvideTheme();
275        mGuidanceStylist = onCreateGuidanceStylist();
276        mActionsStylist = onCreateActionsStylist();
277        mButtonActionsStylist = onCreateButtonActionsStylist();
278        onProvideFragmentTransitions();
279    }
280
281    /**
282     * Creates the presenter used to style the guidance panel. The default implementation returns
283     * a basic GuidanceStylist.
284     * @return The GuidanceStylist used in this fragment.
285     */
286    public GuidanceStylist onCreateGuidanceStylist() {
287        return new GuidanceStylist();
288    }
289
290    /**
291     * Creates the presenter used to style the guided actions panel. The default implementation
292     * returns a basic GuidedActionsStylist.
293     * @return The GuidedActionsStylist used in this fragment.
294     */
295    public GuidedActionsStylist onCreateActionsStylist() {
296        return new GuidedActionsStylist();
297    }
298
299    /**
300     * Creates the presenter used to style a sided actions panel for button only.
301     * The default implementation returns a basic GuidedActionsStylist.
302     * @return The GuidedActionsStylist used in this fragment.
303     */
304    public GuidedActionsStylist onCreateButtonActionsStylist() {
305        GuidedActionsStylist stylist = new GuidedActionsStylist();
306        stylist.setAsButtonActions();
307        return stylist;
308    }
309
310    /**
311     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
312     * host Activity's theme should be used.
313     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
314     * host Activity's theme.
315     */
316    public int onProvideTheme() {
317        return -1;
318    }
319
320    /**
321     * Returns the information required to provide guidance to the user. This hook is called during
322     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
323     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
324     * returns a Guidance object with empty fields; subclasses should override.
325     * @param savedInstanceState The saved instance state from onCreateView.
326     * @return The Guidance object representing the information used to guide the user.
327     */
328    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
329        return new Guidance("", "", "", null);
330    }
331
332    /**
333     * Fills out the set of actions available to the user. This hook is called during {@link
334     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
335     * @param actions A non-null, empty list ready to be populated.
336     * @param savedInstanceState The saved instance state from onCreate.
337     */
338    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
339    }
340
341    /**
342     * Fills out the set of actions shown at right available to the user. This hook is called during
343     * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override.
344     * @param actions A non-null, empty list ready to be populated.
345     * @param savedInstanceState The saved instance state from onCreate.
346     */
347    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
348            Bundle savedInstanceState) {
349    }
350
351    /**
352     * Callback invoked when an action is taken by the user. Subclasses should override in
353     * order to act on the user's decisions.
354     * @param action The chosen action.
355     */
356    public void onGuidedActionClicked(GuidedAction action) {
357    }
358
359    /**
360     * Callback invoked when an action in sub actions is taken by the user. Subclasses should
361     * override in order to act on the user's decisions.  Default return value is true to close
362     * the sub actions list.
363     * @param action The chosen action.
364     * @return true to collapse the sub actions list, false to keep it expanded.
365     */
366    public boolean onSubGuidedActionClicked(GuidedAction action) {
367        return true;
368    }
369
370    /**
371     * @return True if the sub actions list is expanded, false otherwise.
372     */
373    public boolean isSubActionsExpanded() {
374        return mActionsStylist.isSubActionsExpanded();
375    }
376
377    /**
378     * Expand a given action's sub actions list.
379     * @param action GuidedAction to expand.
380     * @see GuidedAction#getSubActions()
381     */
382    public void expandSubActions(GuidedAction action) {
383        final int actionPosition = mActions.indexOf(action);
384        if (actionPosition < 0) {
385            return;
386        }
387        mActionsStylist.getActionsGridView().setSelectedPositionSmooth(actionPosition,
388                new ViewHolderTask() {
389            @Override
390            public void run(RecyclerView.ViewHolder vh) {
391                GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder) vh;
392                mActionsStylist.setExpandedViewHolder(avh);
393            }
394        });
395    }
396
397    /**
398     * Collapse sub actions list.
399     * @see GuidedAction#getSubActions()
400     */
401    public void collapseSubActions() {
402        mActionsStylist.setExpandedViewHolder(null);
403    }
404
405    /**
406     * Callback invoked when an action is focused (made to be the current selection) by the user.
407     */
408    @Override
409    public void onGuidedActionFocused(GuidedAction action) {
410    }
411
412    /**
413     * Callback invoked when an action's title or description has been edited.
414     * Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} instead of app wants to
415     * control the next action to focus on.
416     */
417    public void onGuidedActionEdited(GuidedAction action) {
418    }
419
420    /**
421     * Callback invoked when an action's title or description has been edited.  Default
422     * implementation calls {@link #onGuidedActionEdited(GuidedAction)} and returns
423     * {@link GuidedAction#ACTION_ID_NEXT}.
424     *
425     * @param action The action that has been edited.
426     * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
427     * {@link GuidedAction#ACTION_ID_CURRENT}.
428     */
429    public long onGuidedActionEditedAndProceed(GuidedAction action) {
430        onGuidedActionEdited(action);
431        return GuidedAction.ACTION_ID_NEXT;
432    }
433
434    /**
435     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
436     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
437     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
438     * is pressed.
439     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE}
440     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
441     * <p>
442     * Note: currently fragments added using this method must be created programmatically rather
443     * than via XML.
444     * @param fragmentManager The FragmentManager to be used in the transaction.
445     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
446     * @return The ID returned by the call FragmentTransaction.replace.
447     */
448    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) {
449        return add(fragmentManager, fragment, android.R.id.content);
450    }
451
452    /**
453     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
454     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
455     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
456     * is pressed.
457     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE} and
458     * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepSupportFragment)} will be called
459     * to perform shared element transition between GuidedStepSupportFragments.
460     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
461     * <p>
462     * Note: currently fragments added using this method must be created programmatically rather
463     * than via XML.
464     * @param fragmentManager The FragmentManager to be used in the transaction.
465     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
466     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
467     * @return The ID returned by the call FragmentTransaction.replace.
468     */
469    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) {
470        GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager);
471        boolean inGuidedStep = current != null;
472        if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
473                && !inGuidedStep) {
474            // workaround b/22631964 for framework fragment
475            fragmentManager.beginTransaction()
476                .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
477                .commit();
478        }
479        FragmentTransaction ft = fragmentManager.beginTransaction();
480
481        fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
482        ft.addToBackStack(fragment.generateStackEntryName());
483        if (current != null) {
484            fragment.onAddSharedElementTransition(ft, current);
485        }
486        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
487    }
488
489    /**
490     * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka
491     * when the GuidedStepSupportFragment replacing an existing GuidedStepSupportFragment). Default implementation
492     * establishes connections between action background views to morph action background bounds
493     * change from disappearing GuidedStepSupportFragment into this GuidedStepSupportFragment. The default
494     * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this
495     * method when modifying the default layout of {@link GuidedActionsStylist}.
496     *
497     * @see GuidedActionsStylist
498     * @see #onProvideFragmentTransitions()
499     * @param ft The FragmentTransaction to add shared element.
500     * @param disappearing The disappearing fragment.
501     */
502    protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepSupportFragment
503            disappearing) {
504        View fragmentView = disappearing.getView();
505        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
506                R.id.action_fragment_root), "action_fragment_root");
507        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
508                R.id.action_fragment_background), "action_fragment_background");
509        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
510                R.id.action_fragment), "action_fragment");
511        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
512                R.id.guidedactions_root), "guidedactions_root");
513        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
514                R.id.guidedactions_content), "guidedactions_content");
515        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
516                R.id.guidedactions_list_background), "guidedactions_list_background");
517        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
518                R.id.guidedactions_root2), "guidedactions_root2");
519        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
520                R.id.guidedactions_content2), "guidedactions_content2");
521        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
522                R.id.guidedactions_list_background2), "guidedactions_list_background2");
523    }
524
525    private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
526                                                           String transitionName)
527    {
528        if (subView != null)
529            TransitionHelper.addSharedElement(ft, subView, transitionName);
530    }
531
532    /**
533     * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
534     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
535     * returns undefined value if the fragment is not in FragmentManager.
536     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
537     * associated.
538     */
539    String generateStackEntryName() {
540        return generateStackEntryName(getUiStyle(), getClass());
541    }
542
543    /**
544     * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is
545     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
546     * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE}
547     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
548     * associated.
549     */
550    static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
551        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
552            return "";
553        }
554        switch (uiStyle) {
555        case UI_STYLE_REPLACE:
556            return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName();
557        case UI_STYLE_ENTRANCE:
558            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
559        case UI_STYLE_ACTIVITY_ROOT:
560        default:
561            return "";
562        }
563    }
564
565    /**
566     * Returns true if the backstack entry represents GuidedStepSupportFragment with
567     * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepSupportFragment pushed to stack; false
568     * otherwise.
569     * @see #generateStackEntryName(int, Class)
570     * @param backStackEntryName Name of BackStackEntry.
571     * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
572     * false otherwise.
573     */
574    static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
575        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
576    }
577
578    /**
579     * Extract Class name from BackStackEntry name.
580     * @param backStackEntryName Name of BackStackEntry.
581     * @return Class name of GuidedStepSupportFragment.
582     */
583    static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
584        if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
585            return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
586        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
587            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
588        } else {
589            return "";
590        }
591    }
592
593    /**
594     * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
595     * the activity will be dismissed when BACK key is pressed.
596     * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
597     *
598     * Note: currently fragments added using this method must be created programmatically rather
599     * than via XML.
600     * @param activity The Activity to be used to insert GuidedstepFragment.
601     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
602     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
603     * @return The ID returned by the call FragmentTransaction.replace.
604     */
605    public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
606        // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
607        activity.getWindow().getDecorView();
608        FragmentManager fragmentManager = activity.getSupportFragmentManager();
609        FragmentTransaction ft = fragmentManager.beginTransaction();
610        fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
611        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
612    }
613
614    /**
615     * Returns the current GuidedStepSupportFragment on the fragment transaction stack.
616     * @return The current GuidedStepSupportFragment, if any, on the fragment transaction stack.
617     */
618    public static GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(FragmentManager fm) {
619        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
620        if (f instanceof GuidedStepSupportFragment) {
621            return (GuidedStepSupportFragment) f;
622        }
623        return null;
624    }
625
626    /**
627     * Returns the GuidanceStylist that displays guidance information for the user.
628     * @return The GuidanceStylist for this fragment.
629     */
630    public GuidanceStylist getGuidanceStylist() {
631        return mGuidanceStylist;
632    }
633
634    /**
635     * Returns the GuidedActionsStylist that displays the actions the user may take.
636     * @return The GuidedActionsStylist for this fragment.
637     */
638    public GuidedActionsStylist getGuidedActionsStylist() {
639        return mActionsStylist;
640    }
641
642    /**
643     * Returns the list of button GuidedActions that the user may take in this fragment.
644     * @return The list of button GuidedActions for this fragment.
645     */
646    public List<GuidedAction> getButtonActions() {
647        return mButtonActions;
648    }
649
650    /**
651     * Find button GuidedAction by Id.
652     * @param id  Id of the button action to search.
653     * @return  GuidedAction object or null if not found.
654     */
655    public GuidedAction findButtonActionById(long id) {
656        int index = findButtonActionPositionById(id);
657        return index >= 0 ? mButtonActions.get(index) : null;
658    }
659
660    /**
661     * Find button GuidedAction position in array by Id.
662     * @param id  Id of the button action to search.
663     * @return  position of GuidedAction object in array or -1 if not found.
664     */
665    public int findButtonActionPositionById(long id) {
666        if (mButtonActions != null) {
667            for (int i = 0; i < mButtonActions.size(); i++) {
668                GuidedAction action = mButtonActions.get(i);
669                if (mButtonActions.get(i).getId() == id) {
670                    return i;
671                }
672            }
673        }
674        return -1;
675    }
676
677    /**
678     * Returns the GuidedActionsStylist that displays the button actions the user may take.
679     * @return The GuidedActionsStylist for this fragment.
680     */
681    public GuidedActionsStylist getGuidedButtonActionsStylist() {
682        return mButtonActionsStylist;
683    }
684
685    /**
686     * Sets the list of button GuidedActions that the user may take in this fragment.
687     * @param actions The list of button GuidedActions for this fragment.
688     */
689    public void setButtonActions(List<GuidedAction> actions) {
690        mButtonActions = actions;
691        if (mButtonAdapter != null) {
692            mButtonAdapter.setActions(mButtonActions);
693        }
694    }
695
696    /**
697     * Notify an button action has changed and update its UI.
698     * @param position Position of the button GuidedAction in array.
699     */
700    public void notifyButtonActionChanged(int position) {
701        if (mButtonAdapter != null) {
702            mButtonAdapter.notifyItemChanged(position);
703        }
704    }
705
706    /**
707     * Returns the view corresponding to the button action at the indicated position in the list of
708     * actions for this fragment.
709     * @param position The integer position of the button action of interest.
710     * @return The View corresponding to the button action at the indicated position, or null if
711     * that action is not currently onscreen.
712     */
713    public View getButtonActionItemView(int position) {
714        final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView()
715                    .findViewHolderForPosition(position);
716        return holder == null ? null : holder.itemView;
717    }
718
719    /**
720     * Scrolls the action list to the position indicated, selecting that button action's view.
721     * @param position The integer position of the button action of interest.
722     */
723    public void setSelectedButtonActionPosition(int position) {
724        mButtonActionsStylist.getActionsGridView().setSelectedPosition(position);
725    }
726
727    /**
728     * Returns the position if the currently selected button GuidedAction.
729     * @return position The integer position of the currently selected button action.
730     */
731    public int getSelectedButtonActionPosition() {
732        return mButtonActionsStylist.getActionsGridView().getSelectedPosition();
733    }
734
735    /**
736     * Returns the list of GuidedActions that the user may take in this fragment.
737     * @return The list of GuidedActions for this fragment.
738     */
739    public List<GuidedAction> getActions() {
740        return mActions;
741    }
742
743    /**
744     * Find GuidedAction by Id.
745     * @param id  Id of the action to search.
746     * @return  GuidedAction object or null if not found.
747     */
748    public GuidedAction findActionById(long id) {
749        int index = findActionPositionById(id);
750        return index >= 0 ? mActions.get(index) : null;
751    }
752
753    /**
754     * Find GuidedAction position in array by Id.
755     * @param id  Id of the action to search.
756     * @return  position of GuidedAction object in array or -1 if not found.
757     */
758    public int findActionPositionById(long id) {
759        if (mActions != null) {
760            for (int i = 0; i < mActions.size(); i++) {
761                GuidedAction action = mActions.get(i);
762                if (mActions.get(i).getId() == id) {
763                    return i;
764                }
765            }
766        }
767        return -1;
768    }
769
770    /**
771     * Sets the list of GuidedActions that the user may take in this fragment.
772     * @param actions The list of GuidedActions for this fragment.
773     */
774    public void setActions(List<GuidedAction> actions) {
775        mActions = actions;
776        if (mAdapter != null) {
777            mAdapter.setActions(mActions);
778        }
779    }
780
781    /**
782     * Notify an action has changed and update its UI.
783     * @param position Position of the GuidedAction in array.
784     */
785    public void notifyActionChanged(int position) {
786        if (mAdapter != null) {
787            mAdapter.notifyItemChanged(position);
788        }
789    }
790
791    /**
792     * Returns the view corresponding to the action at the indicated position in the list of
793     * actions for this fragment.
794     * @param position The integer position of the action of interest.
795     * @return The View corresponding to the action at the indicated position, or null if that
796     * action is not currently onscreen.
797     */
798    public View getActionItemView(int position) {
799        final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView()
800                    .findViewHolderForPosition(position);
801        return holder == null ? null : holder.itemView;
802    }
803
804    /**
805     * Scrolls the action list to the position indicated, selecting that action's view.
806     * @param position The integer position of the action of interest.
807     */
808    public void setSelectedActionPosition(int position) {
809        mActionsStylist.getActionsGridView().setSelectedPosition(position);
810    }
811
812    /**
813     * Returns the position if the currently selected GuidedAction.
814     * @return position The integer position of the currently selected action.
815     */
816    public int getSelectedActionPosition() {
817        return mActionsStylist.getActionsGridView().getSelectedPosition();
818    }
819
820    /**
821     * Called by Constructor to provide fragment transitions.  The default implementation assigns
822     * transitions based on {@link #getUiStyle()}:
823     * <ul>
824     * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to
825     * start(left) for exit transition, shared element enter transition is set to ChangeBounds.
826     * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit
827     * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition.
828     * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on
829     * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element
830     * enter transition.
831     * </ul>
832     * <p>
833     * The default implementation heavily relies on {@link GuidedActionsStylist} and
834     * {@link GuidanceStylist} layout, app may override this method when modifying the default
835     * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}.
836     * <p>
837     * TIP: because the fragment view is removed during fragment transition, in general app cannot
838     * use two Visibility transition together. Workaround is to create your own Visibility
839     * transition that controls multiple animators (e.g. slide and fade animation in one Transition
840     * class).
841     */
842    protected void onProvideFragmentTransitions() {
843        if (Build.VERSION.SDK_INT >= 21) {
844            final int uiStyle = getUiStyle();
845            if (uiStyle == UI_STYLE_REPLACE) {
846                Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END);
847                TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true);
848                TransitionHelper.setEnterTransition(this, enterTransition);
849
850                Object changeBounds = TransitionHelper.createChangeBounds(false);
851                TransitionHelper.setSharedElementEnterTransition(this, changeBounds);
852            } else if (uiStyle == UI_STYLE_ENTRANCE) {
853                if (entranceTransitionType == SLIDE_FROM_SIDE) {
854                    Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
855                            TransitionHelper.FADE_OUT);
856                    TransitionHelper.include(fade, R.id.guidedstep_background);
857                    Object slideFromSide = TransitionHelper.createFadeAndShortSlide(Gravity.END | Gravity.START);
858                    TransitionHelper.include(slideFromSide, R.id.content_fragment);
859                    TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
860                    Object enterTransition = TransitionHelper.createTransitionSet(false);
861                    TransitionHelper.addTransition(enterTransition, fade);
862                    TransitionHelper.addTransition(enterTransition, slideFromSide);
863                    TransitionHelper.setEnterTransition(this, enterTransition);
864                } else {
865                    Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(Gravity.BOTTOM);
866                    TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
867                    Object enterTransition = TransitionHelper.createTransitionSet(false);
868                    TransitionHelper.addTransition(enterTransition, slideFromBottom);
869                    TransitionHelper.setEnterTransition(this, enterTransition);
870                }
871                // No shared element transition
872                TransitionHelper.setSharedElementEnterTransition(this, null);
873            } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
874                // for Activity root, we dont need enter transition, use activity transition
875                TransitionHelper.setEnterTransition(this, null);
876                // No shared element transition
877                TransitionHelper.setSharedElementEnterTransition(this, null);
878            }
879            // exitTransition is same for all style
880            Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START);
881            TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true);
882            TransitionHelper.setExitTransition(this, exitTransition);
883        }
884    }
885
886    /**
887     * Called by onCreateView to inflate background view.  Default implementation loads view
888     * from {@link R.layout#lb_guidedstep_background} which holds a reference to
889     * guidedStepBackground.
890     * @param inflater LayoutInflater to load background view.
891     * @param container Parent view of background view.
892     * @param savedInstanceState
893     * @return Created background view or null if no background.
894     */
895    public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
896            Bundle savedInstanceState) {
897        return inflater.inflate(R.layout.lb_guidedstep_background, container, false);
898    }
899
900    /**
901     * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment
902     * is first initialized. UI style is used to choose different fragment transition animations and
903     * determine if this is the first GuidedStepSupportFragment on backstack. In most cases app does not
904     * directly call this method, app calls helper function
905     * {@link #add(FragmentManager, GuidedStepSupportFragment, int)}. However if the app creates Fragment
906     * transaction and controls backstack by itself, it would need call setUiStyle() to select the
907     * fragment transition to use.
908     *
909     * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
910     *        {@link #UI_STYLE_ENTRANCE}.
911     */
912    public void setUiStyle(int style) {
913        int oldStyle = getUiStyle();
914        Bundle arguments = getArguments();
915        boolean isNew = false;
916        if (arguments == null) {
917            arguments = new Bundle();
918            isNew = true;
919        }
920        arguments.putInt(EXTRA_UI_STYLE, style);
921        // call setArgument() will validate if the fragment is already added.
922        if (isNew) {
923            setArguments(arguments);
924        }
925        if (style != oldStyle) {
926            onProvideFragmentTransitions();
927        }
928    }
929
930    /**
931     * Read UI style from fragment arguments.  Default value is {@link #UI_STYLE_ENTRANCE} when
932     * fragment is first initialized.  UI style is used to choose different fragment transition
933     * animations and determine if this is the first GuidedStepSupportFragment on backstack.
934     *
935     * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
936     * {@link #UI_STYLE_ENTRANCE}.
937     * @see #onProvideFragmentTransitions()
938     */
939    public int getUiStyle() {
940        Bundle b = getArguments();
941        if (b == null) return UI_STYLE_ENTRANCE;
942        return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE);
943    }
944
945    /**
946     * {@inheritDoc}
947     */
948    @Override
949    public void onCreate(Bundle savedInstanceState) {
950        super.onCreate(savedInstanceState);
951        if (DEBUG) Log.v(TAG, "onCreate");
952        // Set correct transition from saved arguments.
953        onProvideFragmentTransitions();
954        Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments();
955        if (state != null) {
956            if (mSelectedIndex == -1) {
957                mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1);
958            }
959        }
960        ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
961        onCreateActions(actions, savedInstanceState);
962        setActions(actions);
963        ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
964        onCreateButtonActions(buttonActions, savedInstanceState);
965        setButtonActions(buttonActions);
966    }
967
968    /**
969     * {@inheritDoc}
970     */
971    @Override
972    public void onDestroyView() {
973        mGuidanceStylist.onDestroyView();
974        mActionsStylist.onDestroyView();
975        mButtonActionsStylist.onDestroyView();
976        mAdapter = null;
977        mSubAdapter =  null;
978        mButtonAdapter = null;
979        mAdapterGroup = null;
980        super.onDestroyView();
981    }
982
983    /**
984     * {@inheritDoc}
985     */
986    @Override
987    public View onCreateView(LayoutInflater inflater, ViewGroup container,
988            Bundle savedInstanceState) {
989        if (DEBUG) Log.v(TAG, "onCreateView");
990
991        resolveTheme();
992        inflater = getThemeInflater(inflater);
993
994        GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
995                R.layout.lb_guidedstep_fragment, container, false);
996
997        root.setFocusOutStart(isFocusOutStartAllowed());
998        root.setFocusOutEnd(isFocusOutEndAllowed());
999
1000        ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
1001        ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
1002
1003        Guidance guidance = onCreateGuidance(savedInstanceState);
1004        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
1005        guidanceContainer.addView(guidanceView);
1006
1007        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
1008        actionContainer.addView(actionsView);
1009
1010        View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
1011        actionContainer.addView(buttonActionsView);
1012
1013        GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
1014
1015                @Override
1016                public void onImeOpen() {
1017                    runImeAnimations(true);
1018                }
1019
1020                @Override
1021                public void onImeClose() {
1022                    runImeAnimations(false);
1023                }
1024
1025                @Override
1026                public long onGuidedActionEdited(GuidedAction action) {
1027                    return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action);
1028                }
1029        };
1030
1031        mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
1032            @Override
1033            public void onGuidedActionClicked(GuidedAction action) {
1034                GuidedStepSupportFragment.this.onGuidedActionClicked(action);
1035                if (isSubActionsExpanded()) {
1036                    collapseSubActions();
1037                } else if (action.hasSubActions()) {
1038                    expandSubActions(action);
1039                }
1040            }
1041        }, this, mActionsStylist, false);
1042        mButtonAdapter =
1043                new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
1044                    @Override
1045                    public void onGuidedActionClicked(GuidedAction action) {
1046                        GuidedStepSupportFragment.this.onGuidedActionClicked(action);
1047                    }
1048                }, this, mButtonActionsStylist, false);
1049        mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
1050            @Override
1051            public void onGuidedActionClicked(GuidedAction action) {
1052                if (mActionsStylist.isInExpandTransition()) {
1053                    return;
1054                }
1055                if (GuidedStepSupportFragment.this.onSubGuidedActionClicked(action)) {
1056                    collapseSubActions();
1057                }
1058            }
1059        }, this, mActionsStylist, true);
1060        mAdapterGroup = new GuidedActionAdapterGroup();
1061        mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
1062        mAdapterGroup.addAdpter(mSubAdapter, null);
1063        mAdapterGroup.setEditListener(editListener);
1064        mActionsStylist.setEditListener(editListener);
1065
1066        mActionsStylist.getActionsGridView().setAdapter(mAdapter);
1067        if (mActionsStylist.getSubActionsGridView() != null) {
1068            mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
1069        }
1070        mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
1071        if (mButtonActions.size() == 0) {
1072            // when there is no button actions, we dont need show the second panel, but keep
1073            // the width zero to run ChangeBounds transition.
1074            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
1075                    buttonActionsView.getLayoutParams();
1076            lp.weight = 0;
1077            buttonActionsView.setLayoutParams(lp);
1078        } else {
1079            // when there are two actions panel, we need adjust the weight of action to
1080            // guidedActionContentWidthWeightTwoPanels.
1081            Context ctx = mThemeWrapper != null ? mThemeWrapper : getActivity();
1082            TypedValue typedValue = new TypedValue();
1083            if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
1084                    typedValue, true)) {
1085                View actionsRoot = root.findViewById(R.id.action_fragment_root);
1086                float weight = typedValue.getFloat();
1087                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot
1088                        .getLayoutParams();
1089                lp.weight = weight;
1090                actionsRoot.setLayoutParams(lp);
1091            }
1092        }
1093
1094        int pos = (mSelectedIndex >= 0 && mSelectedIndex < mActions.size()) ?
1095                mSelectedIndex : getFirstCheckedAction();
1096        setSelectedActionPosition(pos);
1097
1098        setSelectedButtonActionPosition(0);
1099
1100        // Add the background view.
1101        View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
1102        if (backgroundView != null) {
1103            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
1104                R.id.guidedstep_background_view_root);
1105            backgroundViewRoot.addView(backgroundView, 0);
1106        }
1107        return root;
1108    }
1109
1110    /**
1111     * {@inheritDoc}
1112     */
1113    @Override
1114    public void onSaveInstanceState(Bundle outState) {
1115        super.onSaveInstanceState(outState);
1116        outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
1117                (mActionsStylist.getActionsGridView() != null) ?
1118                        getSelectedActionPosition() : mSelectedIndex);
1119    }
1120
1121    private static boolean isGuidedStepTheme(Context context) {
1122        int resId = R.attr.guidedStepThemeFlag;
1123        TypedValue typedValue = new TypedValue();
1124        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
1125        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
1126        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
1127    }
1128
1129    /**
1130     * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if
1131     * GuidedStepSupportFragments were started in a separate activity.  Pops all stack entries including
1132     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
1133     * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepSupportFragment,
1134     * int)} which sets up the stack entry name for finding which fragment we need to pop back to.
1135     */
1136    public void finishGuidedStepSupportFragments() {
1137        final FragmentManager fragmentManager = getFragmentManager();
1138        final int entryCount = fragmentManager.getBackStackEntryCount();
1139        if (entryCount > 0) {
1140            for (int i = entryCount - 1; i >= 0; i--) {
1141                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
1142                if (isStackEntryUiStyleEntrance(entry.getName())) {
1143                    GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager);
1144                    if (top != null) {
1145                        top.setUiStyle(UI_STYLE_ENTRANCE);
1146                    }
1147                    fragmentManager.popBackStack(entry.getId(),
1148                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
1149                    return;
1150                }
1151            }
1152        }
1153        ActivityCompat.finishAfterTransition(getActivity());
1154    }
1155
1156    /**
1157     * Convenient method to pop to fragment with Given class.
1158     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepSupportFragment to pop to.
1159     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
1160     */
1161    public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) {
1162        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
1163            return;
1164        }
1165        final FragmentManager fragmentManager = getFragmentManager();
1166        final int entryCount = fragmentManager.getBackStackEntryCount();
1167        String className = guidedStepFragmentClass.getName();
1168        if (entryCount > 0) {
1169            for (int i = entryCount - 1; i >= 0; i--) {
1170                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
1171                String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName());
1172                if (className.equals(entryClassName)) {
1173                    fragmentManager.popBackStack(entry.getId(), flags);
1174                    return;
1175                }
1176            }
1177        }
1178    }
1179
1180    /**
1181     * Returns true if allows focus out of start edge of GuidedStepSupportFragment, false otherwise.
1182     * Default value is false, the reason is to disable FocusFinder to find focusable views
1183     * beneath content of GuidedStepSupportFragment.  Subclass may override.
1184     * @return True if allows focus out of start edge of GuidedStepSupportFragment.
1185     */
1186    public boolean isFocusOutStartAllowed() {
1187        return false;
1188    }
1189
1190    /**
1191     * Returns true if allows focus out of end edge of GuidedStepSupportFragment, false otherwise.
1192     * Default value is false, the reason is to disable FocusFinder to find focusable views
1193     * beneath content of GuidedStepSupportFragment.  Subclass may override.
1194     * @return True if allows focus out of end edge of GuidedStepSupportFragment.
1195     */
1196    public boolean isFocusOutEndAllowed() {
1197        return false;
1198    }
1199
1200    /**
1201     * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
1202     * Currently we provide 2 different variations for animation - slide in from
1203     * side (default) or bottom.
1204     *
1205     * Ideally we can retireve the screen mode settings from the theme attribute
1206     * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
1207     * determine the transition. But the fragment context to retrieve the theme
1208     * isn't available on platform v23 or earlier.
1209     *
1210     * For now clients(subclasses) can call this method inside the contructor.
1211     * @hide
1212     */
1213    public void setEntranceTransitionType(int transitionType) {
1214      this.entranceTransitionType = transitionType;
1215    }
1216
1217    private void resolveTheme() {
1218        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
1219        // replace the theme with its value.
1220        FragmentActivity activity = getActivity();
1221        if (mTheme == -1 && !isGuidedStepTheme(activity)) {
1222            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
1223            // exists, replace the theme with its value.
1224            int resId = R.attr.guidedStepTheme;
1225            TypedValue typedValue = new TypedValue();
1226            boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true);
1227            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
1228            if (found) {
1229                ContextThemeWrapper themeWrapper =
1230                        new ContextThemeWrapper(activity, typedValue.resourceId);
1231                if (isGuidedStepTheme(themeWrapper)) {
1232                    mTheme = typedValue.resourceId;
1233                    mThemeWrapper = themeWrapper;
1234                } else {
1235                    found = false;
1236                    mThemeWrapper = null;
1237                }
1238            }
1239            if (!found) {
1240                Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set.");
1241            }
1242        } else if (mTheme != -1) {
1243            mThemeWrapper = new ContextThemeWrapper(activity, mTheme);
1244        }
1245    }
1246
1247    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
1248        if (mTheme == -1) {
1249            return inflater;
1250        } else {
1251            return inflater.cloneInContext(mThemeWrapper);
1252        }
1253    }
1254
1255    private int getFirstCheckedAction() {
1256        for (int i = 0, size = mActions.size(); i < size; i++) {
1257            if (mActions.get(i).isChecked()) {
1258                return i;
1259            }
1260        }
1261        return 0;
1262    }
1263
1264    private void runImeAnimations(boolean entering) {
1265        ArrayList<Animator> animators = new ArrayList<Animator>();
1266        if (entering) {
1267            mGuidanceStylist.onImeAppearing(animators);
1268            mActionsStylist.onImeAppearing(animators);
1269            mButtonActionsStylist.onImeAppearing(animators);
1270        } else {
1271            mGuidanceStylist.onImeDisappearing(animators);
1272            mActionsStylist.onImeDisappearing(animators);
1273            mButtonActionsStylist.onImeDisappearing(animators);
1274        }
1275        AnimatorSet set = new AnimatorSet();
1276        set.playTogether(animators);
1277        set.start();
1278    }
1279
1280}
1281