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