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