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