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