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