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