Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 package android.support.v17.leanback.widget;
     15 
     16 import android.animation.Animator;
     17 import android.animation.AnimatorInflater;
     18 import android.content.Context;
     19 import android.content.res.TypedArray;
     20 import android.graphics.drawable.Drawable;
     21 import android.os.Build.VERSION;
     22 import android.support.annotation.NonNull;
     23 import android.support.v17.leanback.R;
     24 import android.support.v17.leanback.transition.TransitionHelper;
     25 import android.support.v17.leanback.transition.TransitionListener;
     26 import android.support.v17.leanback.widget.GuidedActionAdapter.EditListener;
     27 import android.support.v17.leanback.widget.picker.DatePicker;
     28 import android.support.v4.content.ContextCompat;
     29 import android.support.v7.widget.RecyclerView;
     30 import android.text.TextUtils;
     31 import android.util.TypedValue;
     32 import android.view.Gravity;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.View.AccessibilityDelegate;
     36 import android.view.ViewGroup;
     37 import android.view.WindowManager;
     38 import android.view.accessibility.AccessibilityEvent;
     39 import android.view.accessibility.AccessibilityNodeInfo;
     40 import android.view.inputmethod.EditorInfo;
     41 import android.widget.Checkable;
     42 import android.widget.EditText;
     43 import android.widget.ImageView;
     44 import android.widget.TextView;
     45 
     46 import java.util.Calendar;
     47 import java.util.Collections;
     48 import java.util.List;
     49 
     50 import static android.support.v17.leanback.widget.GuidedAction.EDITING_ACTIVATOR_VIEW;
     51 import static android.support.v17.leanback.widget.GuidedAction.EDITING_DESCRIPTION;
     52 import static android.support.v17.leanback.widget.GuidedAction.EDITING_NONE;
     53 import static android.support.v17.leanback.widget.GuidedAction.EDITING_TITLE;
     54 
     55 /**
     56  * GuidedActionsStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment}
     57  * to supply the right-side panel where users can take actions. It consists of a container for the
     58  * list of actions, and a stationary selector view that indicates visually the location of focus.
     59  * GuidedActionsStylist has two different layouts: default is for normal actions including text,
     60  * radio, checkbox, DatePicker, etc, the other when {@link #setAsButtonActions()} is called is
     61  * recommended for button actions such as "yes", "no".
     62  * <p>
     63  * Many aspects of the base GuidedActionsStylist can be customized through theming; see the
     64  * theme attributes below. Note that these attributes are not set on individual elements in layout
     65  * XML, but instead would be set in a custom theme. See
     66  * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>
     67  * for more information.
     68  * <p>
     69  * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to
     70  * override the {@link #onProvideLayoutId} method to change the layout used to display the
     71  * list container and selector; override {@link #onProvideItemLayoutId(int)} and
     72  * {@link #getItemViewType(GuidedAction)} method to change the layout used to display each action.
     73  * <p>
     74  * To support a "click to activate" view similar to DatePicker, app needs:
     75  * <li> Override {@link #onProvideItemLayoutId(int)} and {@link #getItemViewType(GuidedAction)},
     76  * provides a layout id for the action.
     77  * <li> The layout must include a widget with id "guidedactions_activator_item", the widget is
     78  * toggled edit mode by {@link View#setActivated(boolean)}.
     79  * <li> Override {@link #onBindActivatorView(ViewHolder, GuidedAction)} to populate values into View.
     80  * <li> Override {@link #onUpdateActivatorView(ViewHolder, GuidedAction)} to update action.
     81  * <p>
     82  * Note: If an alternate list layout is provided, the following view IDs must be supplied:
     83  * <ul>
     84  * <li>{@link android.support.v17.leanback.R.id#guidedactions_list}</li>
     85  * </ul><p>
     86  * These view IDs must be present in order for the stylist to function. The list ID must correspond
     87  * to a {@link VerticalGridView} or subclass.
     88  * <p>
     89  * If an alternate item layout is provided, the following view IDs should be used to refer to base
     90  * elements:
     91  * <ul>
     92  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_content}</li>
     93  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_title}</li>
     94  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_description}</li>
     95  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_icon}</li>
     96  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_checkmark}</li>
     97  * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_chevron}</li>
     98  * </ul><p>
     99  * These view IDs are allowed to be missing, in which case the corresponding views in {@link
    100  * GuidedActionsStylist.ViewHolder} will be null.
    101  * <p>
    102  * In order to support editable actions, the view associated with guidedactions_item_title should
    103  * be a subclass of {@link android.widget.EditText}, and should satisfy the {@link
    104  * ImeKeyMonitor} interface.
    105  *
    106  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation
    107  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation
    108  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorDrawable
    109  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsListStyle
    110  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedSubActionsListStyle
    111  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedButtonActionsListStyle
    112  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContainerStyle
    113  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemCheckmarkStyle
    114  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemIconStyle
    115  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContentStyle
    116  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemTitleStyle
    117  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemDescriptionStyle
    118  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemChevronStyle
    119  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionPressedAnimation
    120  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUnpressedAnimation
    121  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionEnabledChevronAlpha
    122  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDisabledChevronAlpha
    123  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMinLines
    124  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMaxLines
    125  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDescriptionMinLines
    126  * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionVerticalPadding
    127  * @see android.R.styleable#Theme_listChoiceIndicatorSingle
    128  * @see android.R.styleable#Theme_listChoiceIndicatorMultiple
    129  * @see android.support.v17.leanback.app.GuidedStepFragment
    130  * @see GuidedAction
    131  */
    132 public class GuidedActionsStylist implements FragmentAnimationProvider {
    133 
    134     /**
    135      * Default viewType that associated with default layout Id for the action item.
    136      * @see #getItemViewType(GuidedAction)
    137      * @see #onProvideItemLayoutId(int)
    138      * @see #onCreateViewHolder(ViewGroup, int)
    139      */
    140     public static final int VIEW_TYPE_DEFAULT = 0;
    141 
    142     /**
    143      * ViewType for DatePicker.
    144      */
    145     public static final int VIEW_TYPE_DATE_PICKER = 1;
    146 
    147     final static ItemAlignmentFacet sGuidedActionItemAlignFacet;
    148     static {
    149         sGuidedActionItemAlignFacet = new ItemAlignmentFacet();
    150         ItemAlignmentFacet.ItemAlignmentDef alignedDef = new ItemAlignmentFacet.ItemAlignmentDef();
    151         alignedDef.setItemAlignmentViewId(R.id.guidedactions_item_title);
    152         alignedDef.setAlignedToTextViewBaseline(true);
    153         alignedDef.setItemAlignmentOffset(0);
    154         alignedDef.setItemAlignmentOffsetWithPadding(true);
    155         alignedDef.setItemAlignmentOffsetPercent(0);
    156         sGuidedActionItemAlignFacet.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]{alignedDef});
    157     }
    158 
    159     /**
    160      * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link
    161      * GuidedActionsStylist} may also wish to subclass this in order to add fields.
    162      * @see GuidedAction
    163      */
    164     public static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
    165 
    166         private GuidedAction mAction;
    167         private View mContentView;
    168         private TextView mTitleView;
    169         private TextView mDescriptionView;
    170         private View mActivatorView;
    171         private ImageView mIconView;
    172         private ImageView mCheckmarkView;
    173         private ImageView mChevronView;
    174         private int mEditingMode = EDITING_NONE;
    175         private final boolean mIsSubAction;
    176 
    177         final AccessibilityDelegate mDelegate = new AccessibilityDelegate() {
    178             @Override
    179             public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
    180                 super.onInitializeAccessibilityEvent(host, event);
    181                 event.setChecked(mAction != null && mAction.isChecked());
    182             }
    183 
    184             @Override
    185             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
    186                 super.onInitializeAccessibilityNodeInfo(host, info);
    187                 info.setCheckable(
    188                         mAction != null && mAction.getCheckSetId() != GuidedAction.NO_CHECK_SET);
    189                 info.setChecked(mAction != null && mAction.isChecked());
    190             }
    191         };
    192 
    193         /**
    194          * Constructs an ViewHolder and caches the relevant subviews.
    195          */
    196         public ViewHolder(View v) {
    197             this(v, false);
    198         }
    199 
    200         /**
    201          * Constructs an ViewHolder for sub action and caches the relevant subviews.
    202          */
    203         public ViewHolder(View v, boolean isSubAction) {
    204             super(v);
    205 
    206             mContentView = v.findViewById(R.id.guidedactions_item_content);
    207             mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title);
    208             mActivatorView = v.findViewById(R.id.guidedactions_activator_item);
    209             mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description);
    210             mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon);
    211             mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark);
    212             mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron);
    213             mIsSubAction = isSubAction;
    214 
    215             v.setAccessibilityDelegate(mDelegate);
    216         }
    217 
    218         /**
    219          * Returns the content view within this view holder's view, where title and description are
    220          * shown.
    221          */
    222         public View getContentView() {
    223             return mContentView;
    224         }
    225 
    226         /**
    227          * Returns the title view within this view holder's view.
    228          */
    229         public TextView getTitleView() {
    230             return mTitleView;
    231         }
    232 
    233         /**
    234          * Convenience method to return an editable version of the title, if possible,
    235          * or null if the title view isn't an EditText.
    236          */
    237         public EditText getEditableTitleView() {
    238             return (mTitleView instanceof EditText) ? (EditText)mTitleView : null;
    239         }
    240 
    241         /**
    242          * Returns the description view within this view holder's view.
    243          */
    244         public TextView getDescriptionView() {
    245             return mDescriptionView;
    246         }
    247 
    248         /**
    249          * Convenience method to return an editable version of the description, if possible,
    250          * or null if the description view isn't an EditText.
    251          */
    252         public EditText getEditableDescriptionView() {
    253             return (mDescriptionView instanceof EditText) ? (EditText)mDescriptionView : null;
    254         }
    255 
    256         /**
    257          * Returns the icon view within this view holder's view.
    258          */
    259         public ImageView getIconView() {
    260             return mIconView;
    261         }
    262 
    263         /**
    264          * Returns the checkmark view within this view holder's view.
    265          */
    266         public ImageView getCheckmarkView() {
    267             return mCheckmarkView;
    268         }
    269 
    270         /**
    271          * Returns the chevron view within this view holder's view.
    272          */
    273         public ImageView getChevronView() {
    274             return mChevronView;
    275         }
    276 
    277         /**
    278          * Returns true if in editing title, description, or activator View, false otherwise.
    279          */
    280         public boolean isInEditing() {
    281             return mEditingMode != EDITING_NONE;
    282         }
    283 
    284         /**
    285          * Returns true if in editing title, description, so IME would be open.
    286          * @return True if in editing title, description, so IME would be open, false otherwise.
    287          */
    288         public boolean isInEditingText() {
    289             return mEditingMode == EDITING_TITLE || mEditingMode == EDITING_DESCRIPTION;
    290         }
    291 
    292         /**
    293          * Returns true if the TextView is in editing title, false otherwise.
    294          */
    295         public boolean isInEditingTitle() {
    296             return mEditingMode == EDITING_TITLE;
    297         }
    298 
    299         /**
    300          * Returns true if the TextView is in editing description, false otherwise.
    301          */
    302         public boolean isInEditingDescription() {
    303             return mEditingMode == EDITING_DESCRIPTION;
    304         }
    305 
    306         /**
    307          * Returns true if is in editing activator view with id guidedactions_activator_item, false
    308          * otherwise.
    309          */
    310         public boolean isInEditingActivatorView() {
    311             return mEditingMode == EDITING_ACTIVATOR_VIEW;
    312         }
    313 
    314         /**
    315          * @return Current editing title view or description view or activator view or null if not
    316          * in editing.
    317          */
    318         public View getEditingView() {
    319             switch(mEditingMode) {
    320             case EDITING_TITLE:
    321                 return mTitleView;
    322             case EDITING_DESCRIPTION:
    323                 return mDescriptionView;
    324             case EDITING_ACTIVATOR_VIEW:
    325                 return mActivatorView;
    326             case EDITING_NONE:
    327             default:
    328                 return null;
    329             }
    330         }
    331 
    332         /**
    333          * @return True if bound action is inside {@link GuidedAction#getSubActions()}, false
    334          * otherwise.
    335          */
    336         public boolean isSubAction() {
    337             return mIsSubAction;
    338         }
    339 
    340         /**
    341          * @return Currently bound action.
    342          */
    343         public GuidedAction getAction() {
    344             return mAction;
    345         }
    346 
    347         void setActivated(boolean activated) {
    348             mActivatorView.setActivated(activated);
    349             if (itemView instanceof GuidedActionItemContainer) {
    350                 ((GuidedActionItemContainer) itemView).setFocusOutAllowed(!activated);
    351             }
    352         }
    353 
    354         @Override
    355         public Object getFacet(Class<?> facetClass) {
    356             if (facetClass == ItemAlignmentFacet.class) {
    357                 return sGuidedActionItemAlignFacet;
    358             }
    359             return null;
    360         }
    361     }
    362 
    363     private static String TAG = "GuidedActionsStylist";
    364 
    365     private ViewGroup mMainView;
    366     private VerticalGridView mActionsGridView;
    367     private VerticalGridView mSubActionsGridView;
    368     private View mBgView;
    369     private View mContentView;
    370     private boolean mButtonActions;
    371 
    372     // Cached values from resources
    373     private float mEnabledTextAlpha;
    374     private float mDisabledTextAlpha;
    375     private float mEnabledDescriptionAlpha;
    376     private float mDisabledDescriptionAlpha;
    377     private float mEnabledChevronAlpha;
    378     private float mDisabledChevronAlpha;
    379     private int mTitleMinLines;
    380     private int mTitleMaxLines;
    381     private int mDescriptionMinLines;
    382     private int mVerticalPadding;
    383     private int mDisplayHeight;
    384 
    385     private EditListener mEditListener;
    386 
    387     private GuidedAction mExpandedAction = null;
    388     private Object mExpandTransition;
    389 
    390     /**
    391      * Creates a view appropriate for displaying a list of GuidedActions, using the provided
    392      * inflater and container.
    393      * <p>
    394      * <i>Note: Does not actually add the created view to the container; the caller should do
    395      * this.</i>
    396      * @param inflater The layout inflater to be used when constructing the view.
    397      * @param container The view group to be passed in the call to
    398      * <code>LayoutInflater.inflate</code>.
    399      * @return The view to be added to the caller's view hierarchy.
    400      */
    401     public View onCreateView(LayoutInflater inflater, final ViewGroup container) {
    402         TypedArray ta = inflater.getContext().getTheme().obtainStyledAttributes(
    403                 R.styleable.LeanbackGuidedStepTheme);
    404         float keylinePercent = ta.getFloat(R.styleable.LeanbackGuidedStepTheme_guidedStepKeyline,
    405                 40);
    406         mMainView = (ViewGroup) inflater.inflate(onProvideLayoutId(), container, false);
    407         mContentView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_content2 :
    408                 R.id.guidedactions_content);
    409         mBgView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_list_background2 :
    410                 R.id.guidedactions_list_background);
    411         if (mMainView instanceof VerticalGridView) {
    412             mActionsGridView = (VerticalGridView) mMainView;
    413         } else {
    414             mActionsGridView = (VerticalGridView) mMainView.findViewById(mButtonActions ?
    415                     R.id.guidedactions_list2 : R.id.guidedactions_list);
    416             if (mActionsGridView == null) {
    417                 throw new IllegalStateException("No ListView exists.");
    418             }
    419             mActionsGridView.setWindowAlignmentOffsetPercent(keylinePercent);
    420             mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    421             if (!mButtonActions) {
    422                 mSubActionsGridView = (VerticalGridView) mMainView.findViewById(
    423                         R.id.guidedactions_sub_list);
    424             }
    425         }
    426         mActionsGridView.setFocusable(false);
    427         mActionsGridView.setFocusableInTouchMode(false);
    428 
    429         // Cache widths, chevron alpha values, max and min text lines, etc
    430         Context ctx = mMainView.getContext();
    431         TypedValue val = new TypedValue();
    432         mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha);
    433         mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha);
    434         mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines);
    435         mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines);
    436         mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines);
    437         mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding);
    438         mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE))
    439                 .getDefaultDisplay().getHeight();
    440 
    441         mEnabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string
    442                 .lb_guidedactions_item_unselected_text_alpha));
    443         mDisabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string
    444                 .lb_guidedactions_item_disabled_text_alpha));
    445         mEnabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string
    446                 .lb_guidedactions_item_unselected_description_text_alpha));
    447         mDisabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string
    448                 .lb_guidedactions_item_disabled_description_text_alpha));
    449         return mMainView;
    450     }
    451 
    452     /**
    453      * Choose the layout resource for button actions in {@link #onProvideLayoutId()}.
    454      */
    455     public void setAsButtonActions() {
    456         if (mMainView != null) {
    457             throw new IllegalStateException("setAsButtonActions() must be called before creating "
    458                     + "views");
    459         }
    460         mButtonActions = true;
    461     }
    462 
    463     /**
    464      * Returns true if it is button actions list, false for normal actions list.
    465      * @return True if it is button actions list, false for normal actions list.
    466      */
    467     public boolean isButtonActions() {
    468         return mButtonActions;
    469     }
    470 
    471     /**
    472      * Called when destroy the View created by GuidedActionsStylist.
    473      */
    474     public void onDestroyView() {
    475         mExpandedAction = null;
    476         mExpandTransition = null;
    477         mActionsGridView = null;
    478         mSubActionsGridView = null;
    479         mContentView = null;
    480         mBgView = null;
    481         mMainView = null;
    482     }
    483 
    484     /**
    485      * Returns the VerticalGridView that displays the list of GuidedActions.
    486      * @return The VerticalGridView for this presenter.
    487      */
    488     public VerticalGridView getActionsGridView() {
    489         return mActionsGridView;
    490     }
    491 
    492     /**
    493      * Returns the VerticalGridView that displays the sub actions list of an expanded action.
    494      * @return The VerticalGridView that displays the sub actions list of an expanded action.
    495      */
    496     public VerticalGridView getSubActionsGridView() {
    497         return mSubActionsGridView;
    498     }
    499 
    500     /**
    501      * Provides the resource ID of the layout defining the host view for the list of guided actions.
    502      * Subclasses may override to provide their own customized layouts. The base implementation
    503      * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions} or
    504      * {@link android.support.v17.leanback.R.layout#lb_guidedbuttonactions} if
    505      * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain
    506      * matching IDs for any views that should be managed by the base class; this can be achieved by
    507      * starting with a copy of the base layout file.
    508      *
    509      * @return The resource ID of the layout to be inflated to define the host view for the list of
    510      *         GuidedActions.
    511      */
    512     public int onProvideLayoutId() {
    513         return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions;
    514     }
    515 
    516     /**
    517      * Return view type of action, each different type can have differently associated layout Id.
    518      * Default implementation returns {@link #VIEW_TYPE_DEFAULT}.
    519      * @param action  The action object.
    520      * @return View type that used in {@link #onProvideItemLayoutId(int)}.
    521      */
    522     public int getItemViewType(GuidedAction action) {
    523         if (action instanceof GuidedDatePickerAction) {
    524             return VIEW_TYPE_DATE_PICKER;
    525         }
    526         return VIEW_TYPE_DEFAULT;
    527     }
    528 
    529     /**
    530      * Provides the resource ID of the layout defining the view for an individual guided actions.
    531      * Subclasses may override to provide their own customized layouts. The base implementation
    532      * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
    533      * the substituted layout should contain matching IDs for any views that should be managed by
    534      * the base class; this can be achieved by starting with a copy of the base layout file. Note
    535      * that in order for the item to support editing, the title view should both subclass {@link
    536      * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link
    537      * GuidedActionEditText}.  To support different types of Layouts, override {@link
    538      * #onProvideItemLayoutId(int)}.
    539      * @return The resource ID of the layout to be inflated to define the view to display an
    540      * individual GuidedAction.
    541      */
    542     public int onProvideItemLayoutId() {
    543         return R.layout.lb_guidedactions_item;
    544     }
    545 
    546     /**
    547      * Provides the resource ID of the layout defining the view for an individual guided actions.
    548      * Subclasses may override to provide their own customized layouts. The base implementation
    549      * supports:
    550      * <li>{@link android.support.v17.leanback.R.layout#lb_guidedactions_item}
    551      * <li>{{@link android.support.v17.leanback.R.layout#lb_guidedactions_datepicker_item}. If
    552      * overridden, the substituted layout should contain matching IDs for any views that should be
    553      * managed by the base class; this can be achieved by starting with a copy of the base layout
    554      * file. Note that in order for the item to support editing, the title view should both subclass
    555      * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see
    556      * {@link GuidedActionEditText}.
    557      *
    558      * @param viewType View type returned by {@link #getItemViewType(GuidedAction)}
    559      * @return The resource ID of the layout to be inflated to define the view to display an
    560      *         individual GuidedAction.
    561      */
    562     public int onProvideItemLayoutId(int viewType) {
    563         if (viewType == VIEW_TYPE_DEFAULT) {
    564             return onProvideItemLayoutId();
    565         } else if (viewType == VIEW_TYPE_DATE_PICKER) {
    566             return R.layout.lb_guidedactions_datepicker_item;
    567         } else {
    568             throw new RuntimeException("ViewType " + viewType +
    569                     " not supported in GuidedActionsStylist");
    570         }
    571     }
    572 
    573     /**
    574      * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
    575      * may choose to return a subclass of ViewHolder.  To support different view types, override
    576      * {@link #onCreateViewHolder(ViewGroup, int)}
    577      * <p>
    578      * <i>Note: Should not actually add the created view to the parent; the caller will do
    579      * this.</i>
    580      * @param parent The view group to be used as the parent of the new view.
    581      * @return The view to be added to the caller's view hierarchy.
    582      */
    583     public ViewHolder onCreateViewHolder(ViewGroup parent) {
    584         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    585         View v = inflater.inflate(onProvideItemLayoutId(), parent, false);
    586         return new ViewHolder(v, parent == mSubActionsGridView);
    587     }
    588 
    589     /**
    590      * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
    591      * may choose to return a subclass of ViewHolder.
    592      * <p>
    593      * <i>Note: Should not actually add the created view to the parent; the caller will do
    594      * this.</i>
    595      * @param parent The view group to be used as the parent of the new view.
    596      * @param viewType The viewType returned by {@link #getItemViewType(GuidedAction)}
    597      * @return The view to be added to the caller's view hierarchy.
    598      */
    599     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    600         if (viewType == VIEW_TYPE_DEFAULT) {
    601             return onCreateViewHolder(parent);
    602         }
    603         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    604         View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false);
    605         return new ViewHolder(v, parent == mSubActionsGridView);
    606     }
    607 
    608     /**
    609      * Binds a {@link ViewHolder} to a particular {@link GuidedAction}.
    610      * @param vh The view holder to be associated with the given action.
    611      * @param action The guided action to be displayed by the view holder's view.
    612      * @return The view to be added to the caller's view hierarchy.
    613      */
    614     public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
    615 
    616         vh.mAction = action;
    617         if (vh.mTitleView != null) {
    618             vh.mTitleView.setText(action.getTitle());
    619             vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha);
    620             vh.mTitleView.setFocusable(false);
    621             vh.mTitleView.setClickable(false);
    622             vh.mTitleView.setLongClickable(false);
    623         }
    624         if (vh.mDescriptionView != null) {
    625             vh.mDescriptionView.setText(action.getDescription());
    626             vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
    627                     View.GONE : View.VISIBLE);
    628             vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha :
    629                 mDisabledDescriptionAlpha);
    630             vh.mDescriptionView.setFocusable(false);
    631             vh.mDescriptionView.setClickable(false);
    632             vh.mDescriptionView.setLongClickable(false);
    633         }
    634         // Clients might want the check mark view to be gone entirely, in which case, ignore it.
    635         if (vh.mCheckmarkView != null) {
    636             onBindCheckMarkView(vh, action);
    637         }
    638         setIcon(vh.mIconView, action);
    639 
    640         if (action.hasMultilineDescription()) {
    641             if (vh.mTitleView != null) {
    642                 setMaxLines(vh.mTitleView, mTitleMaxLines);
    643                 if (vh.mDescriptionView != null) {
    644                     vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(
    645                             vh.itemView.getContext(), vh.mTitleView));
    646                 }
    647             }
    648         } else {
    649             if (vh.mTitleView != null) {
    650                 setMaxLines(vh.mTitleView, mTitleMinLines);
    651             }
    652             if (vh.mDescriptionView != null) {
    653                 setMaxLines(vh.mDescriptionView, mDescriptionMinLines);
    654             }
    655         }
    656         if (vh.mActivatorView != null) {
    657             onBindActivatorView(vh, action);
    658         }
    659         setEditingMode(vh, action, false);
    660         if (action.isFocusable()) {
    661             vh.itemView.setFocusable(true);
    662             ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    663         } else {
    664             vh.itemView.setFocusable(false);
    665             ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
    666         }
    667         setupImeOptions(vh, action);
    668 
    669         updateChevronAndVisibility(vh);
    670     }
    671 
    672     private static void setMaxLines(TextView view, int maxLines) {
    673         // setSingleLine must be called before setMaxLines because it resets maximum to
    674         // Integer.MAX_VALUE.
    675         if (maxLines == 1) {
    676             view.setSingleLine(true);
    677         } else {
    678             view.setSingleLine(false);
    679             view.setMaxLines(maxLines);
    680         }
    681     }
    682 
    683     /**
    684      * Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options.  Default
    685      * implementation assigns {@link EditorInfo#IME_ACTION_DONE}.  Subclass may override.
    686      * @param vh The view holder to be associated with the given action.
    687      * @param action The guided action to be displayed by the view holder's view.
    688      */
    689     protected void setupImeOptions(ViewHolder vh, GuidedAction action) {
    690         setupNextImeOptions(vh.getEditableTitleView());
    691         setupNextImeOptions(vh.getEditableDescriptionView());
    692     }
    693 
    694     private void setupNextImeOptions(EditText edit) {
    695         if (edit != null) {
    696             edit.setImeOptions(EditorInfo.IME_ACTION_NEXT);
    697         }
    698     }
    699 
    700     public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) {
    701         if (editing != vh.isInEditing() && !isInExpandTransition()) {
    702             onEditingModeChange(vh, action, editing);
    703         }
    704     }
    705 
    706     protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) {
    707         action = vh.getAction();
    708         TextView titleView = vh.getTitleView();
    709         TextView descriptionView = vh.getDescriptionView();
    710         if (editing) {
    711             CharSequence editTitle = action.getEditTitle();
    712             if (titleView != null && editTitle != null) {
    713                 titleView.setText(editTitle);
    714             }
    715             CharSequence editDescription = action.getEditDescription();
    716             if (descriptionView != null && editDescription != null) {
    717                 descriptionView.setText(editDescription);
    718             }
    719             if (action.isDescriptionEditable()) {
    720                 if (descriptionView != null) {
    721                     descriptionView.setVisibility(View.VISIBLE);
    722                     descriptionView.setInputType(action.getDescriptionEditInputType());
    723                 }
    724                 vh.mEditingMode = EDITING_DESCRIPTION;
    725             } else if (action.isEditable()){
    726                 if (titleView != null) {
    727                     titleView.setInputType(action.getEditInputType());
    728                 }
    729                 vh.mEditingMode = EDITING_TITLE;
    730             } else if (vh.mActivatorView != null) {
    731                 onEditActivatorView(vh, action, editing);
    732                 vh.mEditingMode = EDITING_ACTIVATOR_VIEW;
    733             }
    734         } else {
    735             if (titleView != null) {
    736                 titleView.setText(action.getTitle());
    737             }
    738             if (descriptionView != null) {
    739                 descriptionView.setText(action.getDescription());
    740             }
    741             if (vh.mEditingMode == EDITING_DESCRIPTION) {
    742                 if (descriptionView != null) {
    743                     descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
    744                             View.GONE : View.VISIBLE);
    745                     descriptionView.setInputType(action.getDescriptionInputType());
    746                 }
    747             } else if (vh.mEditingMode == EDITING_TITLE) {
    748                 if (titleView != null) {
    749                     titleView.setInputType(action.getInputType());
    750                 }
    751             } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) {
    752                 if (vh.mActivatorView != null) {
    753                     onEditActivatorView(vh, action, editing);
    754                 }
    755             }
    756             vh.mEditingMode = EDITING_NONE;
    757         }
    758     }
    759 
    760     /**
    761      * Animates the view holder's view (or subviews thereof) when the action has had its focus
    762      * state changed.
    763      * @param vh The view holder associated with the relevant action.
    764      * @param focused True if the action has become focused, false if it has lost focus.
    765      */
    766     public void onAnimateItemFocused(ViewHolder vh, boolean focused) {
    767         // No animations for this, currently, because the animation is done on
    768         // mSelectorView
    769     }
    770 
    771     /**
    772      * Animates the view holder's view (or subviews thereof) when the action has had its press
    773      * state changed.
    774      * @param vh The view holder associated with the relevant action.
    775      * @param pressed True if the action has been pressed, false if it has been unpressed.
    776      */
    777     public void onAnimateItemPressed(ViewHolder vh, boolean pressed) {
    778         int attr = pressed ? R.attr.guidedActionPressedAnimation :
    779                 R.attr.guidedActionUnpressedAnimation;
    780         createAnimator(vh.itemView, attr).start();
    781     }
    782 
    783     /**
    784      * Resets the view holder's view to unpressed state.
    785      * @param vh The view holder associated with the relevant action.
    786      */
    787     public void onAnimateItemPressedCancelled(ViewHolder vh) {
    788         createAnimator(vh.itemView, R.attr.guidedActionUnpressedAnimation).end();
    789     }
    790 
    791     /**
    792      * Animates the view holder's view (or subviews thereof) when the action has had its check state
    793      * changed. Default implementation calls setChecked() if {@link ViewHolder#getCheckmarkView()}
    794      * is instance of {@link Checkable}.
    795      *
    796      * @param vh The view holder associated with the relevant action.
    797      * @param checked True if the action has become checked, false if it has become unchecked.
    798      * @see #onBindCheckMarkView(ViewHolder, GuidedAction)
    799      */
    800     public void onAnimateItemChecked(ViewHolder vh, boolean checked) {
    801         if (vh.mCheckmarkView instanceof Checkable) {
    802             ((Checkable) vh.mCheckmarkView).setChecked(checked);
    803         }
    804     }
    805 
    806     /**
    807      * Sets states of check mark view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}
    808      * when action's checkset Id is other than {@link GuidedAction#NO_CHECK_SET}. Default
    809      * implementation assigns drawable loaded from theme attribute
    810      * {@link android.R.attr#listChoiceIndicatorMultiple} for checkbox or
    811      * {@link android.R.attr#listChoiceIndicatorSingle} for radio button. Subclass rarely needs
    812      * override the method, instead app can provide its own drawable that supports transition
    813      * animations, change theme attributes {@link android.R.attr#listChoiceIndicatorMultiple} and
    814      * {@link android.R.attr#listChoiceIndicatorSingle} in {android.support.v17.leanback.R.
    815      * styleable#LeanbackGuidedStepTheme}.
    816      *
    817      * @param vh The view holder associated with the relevant action.
    818      * @param action The GuidedAction object to bind to.
    819      * @see #onAnimateItemChecked(ViewHolder, boolean)
    820      */
    821     public void onBindCheckMarkView(ViewHolder vh, GuidedAction action) {
    822         if (action.getCheckSetId() != GuidedAction.NO_CHECK_SET) {
    823             vh.mCheckmarkView.setVisibility(View.VISIBLE);
    824             int attrId = action.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID ?
    825                     android.R.attr.listChoiceIndicatorMultiple :
    826                     android.R.attr.listChoiceIndicatorSingle;
    827             final Context context = vh.mCheckmarkView.getContext();
    828             Drawable drawable = null;
    829             TypedValue typedValue = new TypedValue();
    830             if (context.getTheme().resolveAttribute(attrId, typedValue, true)) {
    831                 drawable = ContextCompat.getDrawable(context, typedValue.resourceId);
    832             }
    833             vh.mCheckmarkView.setImageDrawable(drawable);
    834             if (vh.mCheckmarkView instanceof Checkable) {
    835                 ((Checkable) vh.mCheckmarkView).setChecked(action.isChecked());
    836             }
    837         } else {
    838             vh.mCheckmarkView.setVisibility(View.GONE);
    839         }
    840     }
    841 
    842     /**
    843      * Performs binding activator view value to action.  Default implementation supports
    844      * GuidedDatePickerAction, subclass may override to add support of other views.
    845      * @param vh ViewHolder of activator view.
    846      * @param action GuidedAction to bind.
    847      */
    848     public void onBindActivatorView(ViewHolder vh, GuidedAction action) {
    849         if (action instanceof GuidedDatePickerAction) {
    850             GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
    851             DatePicker dateView = (DatePicker) vh.mActivatorView;
    852             dateView.setDatePickerFormat(dateAction.getDatePickerFormat());
    853             if (dateAction.getMinDate() != Long.MIN_VALUE) {
    854                 dateView.setMinDate(dateAction.getMinDate());
    855             }
    856             if (dateAction.getMaxDate() != Long.MAX_VALUE) {
    857                 dateView.setMaxDate(dateAction.getMaxDate());
    858             }
    859             Calendar c = Calendar.getInstance();
    860             c.setTimeInMillis(dateAction.getDate());
    861             dateView.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
    862                     c.get(Calendar.DAY_OF_MONTH), false);
    863         }
    864     }
    865 
    866     /**
    867      * Performs updating GuidedAction from activator view.  Default implementation supports
    868      * GuidedDatePickerAction, subclass may override to add support of other views.
    869      * @param vh ViewHolder of activator view.
    870      * @param action GuidedAction to update.
    871      * @return True if value has been updated, false otherwise.
    872      */
    873     public boolean onUpdateActivatorView(ViewHolder vh, GuidedAction action) {
    874         if (action instanceof GuidedDatePickerAction) {
    875             GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
    876             DatePicker dateView = (DatePicker) vh.mActivatorView;
    877             if (dateAction.getDate() != dateView.getDate()) {
    878                 dateAction.setDate(dateView.getDate());
    879                 return true;
    880             }
    881         }
    882         return false;
    883     }
    884 
    885     /**
    886      * Sets listener for reporting view being edited.
    887      * @hide
    888      */
    889     public void setEditListener(EditListener listener) {
    890         mEditListener = listener;
    891     }
    892 
    893     void onEditActivatorView(final ViewHolder vh, final GuidedAction action,
    894             boolean editing) {
    895         if (editing) {
    896             vh.itemView.setFocusable(false);
    897             vh.mActivatorView.requestFocus();
    898             setExpandedViewHolder(vh);
    899             vh.mActivatorView.setOnClickListener(new View.OnClickListener() {
    900                 @Override
    901                 public void onClick(View v) {
    902                     if (!isInExpandTransition()) {
    903                         setEditingMode(vh, action, false);
    904                     }
    905                 }
    906             });
    907         } else {
    908             if (onUpdateActivatorView(vh, action)) {
    909                 if (mEditListener != null) {
    910                     mEditListener.onGuidedActionEditedAndProceed(action);
    911                 }
    912             }
    913             vh.itemView.setFocusable(true);
    914             vh.itemView.requestFocus();
    915             setExpandedViewHolder(null);
    916             vh.mActivatorView.setOnClickListener(null);
    917             vh.mActivatorView.setClickable(false);
    918         }
    919     }
    920 
    921     /**
    922      * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}.
    923      * Subclass may override.
    924      *
    925      * @param vh The view holder associated with the relevant action.
    926      * @param action The GuidedAction object to bind to.
    927      */
    928     public void onBindChevronView(ViewHolder vh, GuidedAction action) {
    929         final boolean hasNext = action.hasNext();
    930         final boolean hasSubActions = action.hasSubActions();
    931         if (hasNext || hasSubActions) {
    932             vh.mChevronView.setVisibility(View.VISIBLE);
    933             vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
    934                     mDisabledChevronAlpha);
    935             if (hasNext) {
    936                 float r = mMainView != null
    937                         && mMainView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? 180f : 0f;
    938                 vh.mChevronView.setRotation(r);
    939             } else if (action == mExpandedAction) {
    940                 vh.mChevronView.setRotation(270);
    941             } else {
    942                 vh.mChevronView.setRotation(90);
    943             }
    944         } else {
    945             vh.mChevronView.setVisibility(View.GONE);
    946 
    947         }
    948     }
    949 
    950     /**
    951      * Expands or collapse the sub actions list view.
    952      * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and
    953      * hide the other items in main list.  When null, collapse the sub actions list.
    954      */
    955     public void setExpandedViewHolder(ViewHolder avh) {
    956         if (isInExpandTransition()) {
    957             return;
    958         }
    959         if (isExpandTransitionSupported()) {
    960             startExpandedTransition(avh);
    961         } else {
    962             onUpdateExpandedViewHolder(avh);
    963         }
    964     }
    965 
    966     /**
    967      * Returns true if it is running an expanding or collapsing transition, false otherwise.
    968      * @return True if it is running an expanding or collapsing transition, false otherwise.
    969      */
    970     public boolean isInExpandTransition() {
    971         return mExpandTransition != null;
    972     }
    973 
    974     /**
    975      * Returns if expand/collapse animation is supported.  When this method returns true,
    976      * {@link #startExpandedTransition(ViewHolder)} will be used.  When this method returns false,
    977      * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called.
    978      * @return True if it is running an expanding or collapsing transition, false otherwise.
    979      */
    980     public boolean isExpandTransitionSupported() {
    981         return VERSION.SDK_INT >= 21;
    982     }
    983 
    984     /**
    985      * Start transition to expand or collapse GuidedActionStylist.
    986      * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
    987      * the GuidedActionStylist will collapse sub actions.
    988      */
    989     public void startExpandedTransition(ViewHolder avh) {
    990         ViewHolder focusAvh = null; // expand / collapse view holder
    991         final int count = mActionsGridView.getChildCount();
    992         for (int i = 0; i < count; i++) {
    993             ViewHolder vh = (ViewHolder) mActionsGridView
    994                     .getChildViewHolder(mActionsGridView.getChildAt(i));
    995             if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) {
    996                 // going to collapse this one.
    997                 focusAvh = vh;
    998                 break;
    999             } else if (avh != null && vh.getAction() == avh.getAction()) {
   1000                 // going to expand this one.
   1001                 focusAvh = vh;
   1002                 break;
   1003             }
   1004         }
   1005         if (focusAvh == null) {
   1006             // huh?
   1007             onUpdateExpandedViewHolder(avh);
   1008             return;
   1009         }
   1010         boolean isSubActionTransition = focusAvh.getAction().hasSubActions();
   1011         Object set = TransitionHelper.createTransitionSet(false);
   1012         float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight() :
   1013                 focusAvh.itemView.getHeight() * 0.5f;
   1014         Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM,
   1015                 slideDistance);
   1016         Object changeFocusItemTransform = TransitionHelper.createChangeTransform();
   1017         Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false);
   1018         Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
   1019                 TransitionHelper.FADE_OUT);
   1020         Object changeGridBounds = TransitionHelper.createChangeBounds(false);
   1021         if (avh == null) {
   1022             TransitionHelper.setStartDelay(slideAndFade, 150);
   1023             TransitionHelper.setStartDelay(changeFocusItemTransform, 100);
   1024             TransitionHelper.setStartDelay(changeFocusItemBounds, 100);
   1025         } else {
   1026             TransitionHelper.setStartDelay(fade, 100);
   1027             TransitionHelper.setStartDelay(changeGridBounds, 100);
   1028             TransitionHelper.setStartDelay(changeFocusItemTransform, 50);
   1029             TransitionHelper.setStartDelay(changeFocusItemBounds, 50);
   1030         }
   1031         for (int i = 0; i < count; i++) {
   1032             ViewHolder vh = (ViewHolder) mActionsGridView
   1033                     .getChildViewHolder(mActionsGridView.getChildAt(i));
   1034             if (vh == focusAvh) {
   1035                 // going to expand/collapse this one.
   1036                 if (isSubActionTransition) {
   1037                     TransitionHelper.include(changeFocusItemTransform, vh.itemView);
   1038                     TransitionHelper.include(changeFocusItemBounds, vh.itemView);
   1039                 }
   1040             } else {
   1041                 // going to slide this item to top / bottom.
   1042                 TransitionHelper.include(slideAndFade, vh.itemView);
   1043                 TransitionHelper.exclude(fade, vh.itemView, true);
   1044             }
   1045         }
   1046         TransitionHelper.include(changeGridBounds, mSubActionsGridView);
   1047         TransitionHelper.addTransition(set, slideAndFade);
   1048         // note that we don't run ChangeBounds for activating view due to the rounding problem
   1049         // of multiple level views ChangeBounds animation causing vertical jittering.
   1050         if (isSubActionTransition) {
   1051             TransitionHelper.addTransition(set, changeFocusItemTransform);
   1052             TransitionHelper.addTransition(set, changeFocusItemBounds);
   1053         }
   1054         TransitionHelper.addTransition(set, fade);
   1055         TransitionHelper.addTransition(set, changeGridBounds);
   1056         mExpandTransition = set;
   1057         TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() {
   1058             @Override
   1059             public void onTransitionEnd(Object transition) {
   1060                 mExpandTransition = null;
   1061             }
   1062         });
   1063         if (avh != null && mSubActionsGridView.getTop() != avh.itemView.getTop()) {
   1064             // For expanding, set the initial position of subActionsGridView before running
   1065             // a ChangeBounds on it.
   1066             final ViewHolder toUpdate = avh;
   1067             mSubActionsGridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
   1068                 @Override
   1069                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
   1070                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
   1071                     if (mSubActionsGridView == null) {
   1072                         return;
   1073                     }
   1074                     mSubActionsGridView.removeOnLayoutChangeListener(this);
   1075                     mMainView.post(new Runnable() {
   1076                         public void run() {
   1077                             if (mMainView == null) {
   1078                                 return;
   1079                             }
   1080                             TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
   1081                             onUpdateExpandedViewHolder(toUpdate);
   1082                         }
   1083                     });
   1084                 }
   1085             });
   1086             ViewGroup.MarginLayoutParams lp =
   1087                     (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
   1088             lp.topMargin = avh.itemView.getTop();
   1089             lp.height = 0;
   1090             mSubActionsGridView.setLayoutParams(lp);
   1091             return;
   1092         }
   1093         TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
   1094         onUpdateExpandedViewHolder(avh);
   1095     }
   1096 
   1097     /**
   1098      * @return True if sub actions list is expanded.
   1099      */
   1100     public boolean isSubActionsExpanded() {
   1101         return mExpandedAction != null;
   1102     }
   1103 
   1104     /**
   1105      * @return Current expanded GuidedAction or null if not expanded.
   1106      */
   1107     public GuidedAction getExpandedAction() {
   1108         return mExpandedAction;
   1109     }
   1110 
   1111     /**
   1112      * Expand or collapse GuidedActionStylist.
   1113      * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
   1114      * the GuidedActionStylist will collapse sub actions.
   1115      */
   1116     public void onUpdateExpandedViewHolder(ViewHolder avh) {
   1117 
   1118         // Note about setting the prune child flag back & forth here: without this, the actions that
   1119         // go off the screen from the top or bottom become invisible forever. This is because once
   1120         // an action is expanded, it takes more space which in turn kicks out some other actions
   1121         // off of the screen. Once, this action is collapsed (after the second click) and the
   1122         // visibility flag is set back to true for all existing actions,
   1123         // the off-the-screen actions are pruned from the view, thus
   1124         // could not be accessed, had we not disabled pruning prior to this.
   1125         if (avh == null) {
   1126             mExpandedAction = null;
   1127             mActionsGridView.setPruneChild(true);
   1128         } else if (avh.getAction() != mExpandedAction) {
   1129             mExpandedAction = avh.getAction();
   1130             mActionsGridView.setPruneChild(false);
   1131         }
   1132         // In expanding mode, notifyItemChange on expanded item will reset the translationY by
   1133         // the default ItemAnimator.  So disable ItemAnimation in expanding mode.
   1134         mActionsGridView.setAnimateChildLayout(false);
   1135         final int count = mActionsGridView.getChildCount();
   1136         for (int i = 0; i < count; i++) {
   1137             ViewHolder vh = (ViewHolder) mActionsGridView
   1138                     .getChildViewHolder(mActionsGridView.getChildAt(i));
   1139             updateChevronAndVisibility(vh);
   1140         }
   1141         if (mSubActionsGridView != null) {
   1142             if (avh != null && avh.getAction().hasSubActions()) {
   1143                 ViewGroup.MarginLayoutParams lp =
   1144                         (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
   1145                 lp.topMargin = avh.itemView.getTop();
   1146                 lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
   1147                 mSubActionsGridView.setLayoutParams(lp);
   1148                 mSubActionsGridView.setVisibility(View.VISIBLE);
   1149                 mSubActionsGridView.requestFocus();
   1150                 mSubActionsGridView.setSelectedPosition(0);
   1151                 ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
   1152                         .setActions(avh.getAction().getSubActions());
   1153             } else if (mSubActionsGridView.getVisibility() == View.VISIBLE) {
   1154                 mSubActionsGridView.setVisibility(View.INVISIBLE);
   1155                 ViewGroup.MarginLayoutParams lp =
   1156                         (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
   1157                 lp.height = 0;
   1158                 mSubActionsGridView.setLayoutParams(lp);
   1159                 ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
   1160                         .setActions(Collections.EMPTY_LIST);
   1161                 mActionsGridView.requestFocus();
   1162             }
   1163         }
   1164     }
   1165 
   1166     private void updateChevronAndVisibility(ViewHolder vh) {
   1167         if (!vh.isSubAction()) {
   1168             if (mExpandedAction == null) {
   1169                 vh.itemView.setVisibility(View.VISIBLE);
   1170                 vh.itemView.setTranslationY(0);
   1171                 if (vh.mActivatorView != null) {
   1172                     vh.setActivated(false);
   1173                 }
   1174             } else if (vh.getAction() == mExpandedAction) {
   1175                 vh.itemView.setVisibility(View.VISIBLE);
   1176                 if (vh.getAction().hasSubActions()) {
   1177                     vh.itemView.setTranslationY(- vh.itemView.getHeight());
   1178                 } else if (vh.mActivatorView != null) {
   1179                     vh.itemView.setTranslationY(0);
   1180                     vh.setActivated(true);
   1181                 }
   1182             } else {
   1183                 vh.itemView.setVisibility(View.INVISIBLE);
   1184                 vh.itemView.setTranslationY(0);
   1185             }
   1186         }
   1187         if (vh.mChevronView != null) {
   1188             onBindChevronView(vh, vh.getAction());
   1189         }
   1190     }
   1191 
   1192     /*
   1193      * ==========================================
   1194      * FragmentAnimationProvider overrides
   1195      * ==========================================
   1196      */
   1197 
   1198     /**
   1199      * {@inheritDoc}
   1200      */
   1201     @Override
   1202     public void onImeAppearing(@NonNull List<Animator> animators) {
   1203     }
   1204 
   1205     /**
   1206      * {@inheritDoc}
   1207      */
   1208     @Override
   1209     public void onImeDisappearing(@NonNull List<Animator> animators) {
   1210     }
   1211 
   1212     /*
   1213      * ==========================================
   1214      * Private methods
   1215      * ==========================================
   1216      */
   1217 
   1218     private float getFloat(Context ctx, TypedValue typedValue, int attrId) {
   1219         ctx.getTheme().resolveAttribute(attrId, typedValue, true);
   1220         // Android resources don't have a native float type, so we have to use strings.
   1221         return Float.valueOf(ctx.getResources().getString(typedValue.resourceId));
   1222     }
   1223 
   1224     private int getInteger(Context ctx, TypedValue typedValue, int attrId) {
   1225         ctx.getTheme().resolveAttribute(attrId, typedValue, true);
   1226         return ctx.getResources().getInteger(typedValue.resourceId);
   1227     }
   1228 
   1229     private int getDimension(Context ctx, TypedValue typedValue, int attrId) {
   1230         ctx.getTheme().resolveAttribute(attrId, typedValue, true);
   1231         return ctx.getResources().getDimensionPixelSize(typedValue.resourceId);
   1232     }
   1233 
   1234     private static Animator createAnimator(View v, int attrId) {
   1235         Context ctx = v.getContext();
   1236         TypedValue typedValue = new TypedValue();
   1237         ctx.getTheme().resolveAttribute(attrId, typedValue, true);
   1238         Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
   1239         animator.setTarget(v);
   1240         return animator;
   1241     }
   1242 
   1243     private boolean setIcon(final ImageView iconView, GuidedAction action) {
   1244         Drawable icon = null;
   1245         if (iconView != null) {
   1246             Context context = iconView.getContext();
   1247             icon = action.getIcon();
   1248             if (icon != null) {
   1249                 // setImageDrawable resets the drawable's level unless we set the view level first.
   1250                 iconView.setImageLevel(icon.getLevel());
   1251                 iconView.setImageDrawable(icon);
   1252                 iconView.setVisibility(View.VISIBLE);
   1253             } else {
   1254                 iconView.setVisibility(View.GONE);
   1255             }
   1256         }
   1257         return icon != null;
   1258     }
   1259 
   1260     /**
   1261      * @return the max height in pixels the description can be such that the
   1262      *         action nicely takes up the entire screen.
   1263      */
   1264     private int getDescriptionMaxHeight(Context context, TextView title) {
   1265         // The 2 multiplier on the title height calculation is a
   1266         // conservative estimate for font padding which can not be
   1267         // calculated at this stage since the view hasn't been rendered yet.
   1268         return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight());
   1269     }
   1270 
   1271 }
   1272