[go: nahoru, domu]

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