Home | History | Annotate | Download | only in app
      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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    373     public boolean isExpanded() {
    374         return mActionsStylist.isExpanded();
    375     }
    376 
    377     /**
    378      * @return True if the sub actions list is expanded, false otherwise.
    379      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
   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
   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
   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
   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
   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      */
   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      */
   1199     final String getButtonAutoRestoreKey(GuidedAction action) {
   1200         return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
   1201     }
   1202 
   1203     static boolean isSaveEnabled(GuidedAction action) {
   1204         return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
   1205     }
   1206 
   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 
   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 
   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 
   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
   1247     public void onSaveInstanceState(Bundle outState) {
   1248         super.onSaveInstanceState(outState);
   1249         onSaveActions(mActions, outState);
   1250         onSaveButtonActions(mButtonActions, outState);
   1251     }
   1252 
   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      */
   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      */
   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      */
   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      */
   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)
   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      */
   1355     public void openInEditMode(GuidedAction action) {
   1356         mActionsStylist.openInEditMode(action);
   1357     }
   1358 
   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 
   1389     private LayoutInflater getThemeInflater(LayoutInflater inflater) {
   1390         if (mThemeWrapper == null) {
   1391             return inflater;
   1392         } else {
   1393             return inflater.cloneInContext(mThemeWrapper);
   1394         }
   1395     }
   1396 
   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 
   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