GuidedActionsStylist.java revision 16ab389e0bd11594059f8164a1477045ee625154
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        mActionsGridView.setFocusable(false);
421        mActionsGridView.setFocusableInTouchMode(false);
422
423        // Cache widths, chevron alpha values, max and min text lines, etc
424        Context ctx = mMainView.getContext();
425        TypedValue val = new TypedValue();
426        mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha);
427        mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha);
428        mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines);
429        mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines);
430        mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines);
431        mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding);
432        mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE))
433                .getDefaultDisplay().getHeight();
434
435        mEnabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string
436                .lb_guidedactions_item_unselected_text_alpha));
437        mDisabledTextAlpha = Float.valueOf(ctx.getResources().getString(R.string
438                .lb_guidedactions_item_disabled_text_alpha));
439        mEnabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string
440                .lb_guidedactions_item_unselected_description_text_alpha));
441        mDisabledDescriptionAlpha = Float.valueOf(ctx.getResources().getString(R.string
442                .lb_guidedactions_item_disabled_description_text_alpha));
443        return mMainView;
444    }
445
446    /**
447     * Choose the layout resource for button actions in {@link #onProvideLayoutId()}.
448     */
449    public void setAsButtonActions() {
450        if (mMainView != null) {
451            throw new IllegalStateException("setAsButtonActions() must be called before creating "
452                    + "views");
453        }
454        mButtonActions = true;
455    }
456
457    /**
458     * Returns true if it is button actions list, false for normal actions list.
459     * @return True if it is button actions list, false for normal actions list.
460     */
461    public boolean isButtonActions() {
462        return mButtonActions;
463    }
464
465    /**
466     * Called when destroy the View created by GuidedActionsStylist.
467     */
468    public void onDestroyView() {
469        mExpandedAction = null;
470        mExpandTransition = null;
471        mActionsGridView = null;
472        mSubActionsGridView = null;
473        mContentView = null;
474        mBgView = null;
475        mMainView = null;
476    }
477
478    /**
479     * Returns the VerticalGridView that displays the list of GuidedActions.
480     * @return The VerticalGridView for this presenter.
481     */
482    public VerticalGridView getActionsGridView() {
483        return mActionsGridView;
484    }
485
486    /**
487     * Returns the VerticalGridView that displays the sub actions list of an expanded action.
488     * @return The VerticalGridView that displays the sub actions list of an expanded action.
489     */
490    public VerticalGridView getSubActionsGridView() {
491        return mSubActionsGridView;
492    }
493
494    /**
495     * Provides the resource ID of the layout defining the host view for the list of guided actions.
496     * Subclasses may override to provide their own customized layouts. The base implementation
497     * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions} or
498     * {@link android.support.v17.leanback.R.layout#lb_guidedbuttonactions} if
499     * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain
500     * matching IDs for any views that should be managed by the base class; this can be achieved by
501     * starting with a copy of the base layout file.
502     *
503     * @return The resource ID of the layout to be inflated to define the host view for the list of
504     *         GuidedActions.
505     */
506    public int onProvideLayoutId() {
507        return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions;
508    }
509
510    /**
511     * Return view type of action, each different type can have differently associated layout Id.
512     * Default implementation returns {@link #VIEW_TYPE_DEFAULT}.
513     * @param action  The action object.
514     * @return View type that used in {@link #onProvideItemLayoutId(int)}.
515     */
516    public int getItemViewType(GuidedAction action) {
517        if (action instanceof GuidedDatePickerAction) {
518            return VIEW_TYPE_DATE_PICKER;
519        }
520        return VIEW_TYPE_DEFAULT;
521    }
522
523    /**
524     * Provides the resource ID of the layout defining the view for an individual guided actions.
525     * Subclasses may override to provide their own customized layouts. The base implementation
526     * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
527     * the substituted layout should contain matching IDs for any views that should be managed by
528     * the base class; this can be achieved by starting with a copy of the base layout file. Note
529     * that in order for the item to support editing, the title view should both subclass {@link
530     * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link
531     * GuidedActionEditText}.  To support different types of Layouts, override {@link
532     * #onProvideItemLayoutId(int)}.
533     * @return The resource ID of the layout to be inflated to define the view to display an
534     * individual GuidedAction.
535     */
536    public int onProvideItemLayoutId() {
537        return R.layout.lb_guidedactions_item;
538    }
539
540    /**
541     * Provides the resource ID of the layout defining the view for an individual guided actions.
542     * Subclasses may override to provide their own customized layouts. The base implementation
543     * supports:
544     * <li>{@link android.support.v17.leanback.R.layout#lb_guidedactions_item}
545     * <li>{{@link android.support.v17.leanback.R.layout#lb_guidedactions_datepicker_item}. If
546     * overridden, the substituted layout should contain matching IDs for any views that should be
547     * managed by the base class; this can be achieved by starting with a copy of the base layout
548     * file. Note that in order for the item to support editing, the title view should both subclass
549     * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see
550     * {@link GuidedActionEditText}.
551     *
552     * @param viewType View type returned by {@link #getItemViewType(GuidedAction)}
553     * @return The resource ID of the layout to be inflated to define the view to display an
554     *         individual GuidedAction.
555     */
556    public int onProvideItemLayoutId(int viewType) {
557        if (viewType == VIEW_TYPE_DEFAULT) {
558            return onProvideItemLayoutId();
559        } else if (viewType == VIEW_TYPE_DATE_PICKER) {
560            return R.layout.lb_guidedactions_datepicker_item;
561        } else {
562            throw new RuntimeException("ViewType " + viewType +
563                    " not supported in GuidedActionsStylist");
564        }
565    }
566
567    /**
568     * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
569     * may choose to return a subclass of ViewHolder.  To support different view types, override
570     * {@link #onCreateViewHolder(ViewGroup, int)}
571     * <p>
572     * <i>Note: Should not actually add the created view to the parent; the caller will do
573     * this.</i>
574     * @param parent The view group to be used as the parent of the new view.
575     * @return The view to be added to the caller's view hierarchy.
576     */
577    public ViewHolder onCreateViewHolder(ViewGroup parent) {
578        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
579        View v = inflater.inflate(onProvideItemLayoutId(), parent, false);
580        return new ViewHolder(v, parent == mSubActionsGridView);
581    }
582
583    /**
584     * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
585     * may choose to return a subclass of ViewHolder.
586     * <p>
587     * <i>Note: Should not actually add the created view to the parent; the caller will do
588     * this.</i>
589     * @param parent The view group to be used as the parent of the new view.
590     * @param viewType The viewType returned by {@link #getItemViewType(GuidedAction)}
591     * @return The view to be added to the caller's view hierarchy.
592     */
593    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
594        if (viewType == VIEW_TYPE_DEFAULT) {
595            return onCreateViewHolder(parent);
596        }
597        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
598        View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false);
599        return new ViewHolder(v, parent == mSubActionsGridView);
600    }
601
602    /**
603     * Binds a {@link ViewHolder} to a particular {@link GuidedAction}.
604     * @param vh The view holder to be associated with the given action.
605     * @param action The guided action to be displayed by the view holder's view.
606     * @return The view to be added to the caller's view hierarchy.
607     */
608    public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
609
610        vh.mAction = action;
611        if (vh.mTitleView != null) {
612            vh.mTitleView.setText(action.getTitle());
613            vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha);
614            vh.mTitleView.setFocusable(false);
615            vh.mTitleView.setClickable(false);
616            vh.mTitleView.setLongClickable(false);
617        }
618        if (vh.mDescriptionView != null) {
619            vh.mDescriptionView.setText(action.getDescription());
620            vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
621                    View.GONE : View.VISIBLE);
622            vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha :
623                mDisabledDescriptionAlpha);
624            vh.mDescriptionView.setFocusable(false);
625            vh.mDescriptionView.setClickable(false);
626            vh.mDescriptionView.setLongClickable(false);
627        }
628        // Clients might want the check mark view to be gone entirely, in which case, ignore it.
629        if (vh.mCheckmarkView != null) {
630            onBindCheckMarkView(vh, action);
631        }
632        setIcon(vh.mIconView, action);
633
634        if (action.hasMultilineDescription()) {
635            if (vh.mTitleView != null) {
636                setMaxLines(vh.mTitleView, mTitleMaxLines);
637                if (vh.mDescriptionView != null) {
638                    vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(
639                            vh.itemView.getContext(), vh.mTitleView));
640                }
641            }
642        } else {
643            if (vh.mTitleView != null) {
644                setMaxLines(vh.mTitleView, mTitleMinLines);
645            }
646            if (vh.mDescriptionView != null) {
647                setMaxLines(vh.mDescriptionView, mDescriptionMinLines);
648            }
649        }
650        if (vh.mActivatorView != null) {
651            onBindActivatorView(vh, action);
652        }
653        setEditingMode(vh, action, false);
654        if (action.isFocusable()) {
655            vh.itemView.setFocusable(true);
656            ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
657        } else {
658            vh.itemView.setFocusable(false);
659            ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
660        }
661        setupImeOptions(vh, action);
662
663        updateChevronAndVisibility(vh);
664    }
665
666    private static void setMaxLines(TextView view, int maxLines) {
667        // setSingleLine must be called before setMaxLines because it resets maximum to
668        // Integer.MAX_VALUE.
669        if (maxLines == 1) {
670            view.setSingleLine(true);
671        } else {
672            view.setSingleLine(false);
673            view.setMaxLines(maxLines);
674        }
675    }
676
677    /**
678     * Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options.  Default
679     * implementation assigns {@link EditorInfo#IME_ACTION_DONE}.  Subclass may override.
680     * @param vh The view holder to be associated with the given action.
681     * @param action The guided action to be displayed by the view holder's view.
682     */
683    protected void setupImeOptions(ViewHolder vh, GuidedAction action) {
684        setupNextImeOptions(vh.getEditableTitleView());
685        setupNextImeOptions(vh.getEditableDescriptionView());
686    }
687
688    private void setupNextImeOptions(EditText edit) {
689        if (edit != null) {
690            edit.setImeOptions(EditorInfo.IME_ACTION_NEXT);
691        }
692    }
693
694    public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) {
695        if (editing != vh.isInEditing() && !isInExpandTransition()) {
696            onEditingModeChange(vh, action, editing);
697        }
698    }
699
700    protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) {
701        action = vh.getAction();
702        TextView titleView = vh.getTitleView();
703        TextView descriptionView = vh.getDescriptionView();
704        if (editing) {
705            CharSequence editTitle = action.getEditTitle();
706            if (titleView != null && editTitle != null) {
707                titleView.setText(editTitle);
708            }
709            CharSequence editDescription = action.getEditDescription();
710            if (descriptionView != null && editDescription != null) {
711                descriptionView.setText(editDescription);
712            }
713            if (action.isDescriptionEditable()) {
714                if (descriptionView != null) {
715                    descriptionView.setVisibility(View.VISIBLE);
716                    descriptionView.setInputType(action.getDescriptionEditInputType());
717                }
718                vh.mEditingMode = EDITING_DESCRIPTION;
719            } else if (action.isEditable()){
720                if (titleView != null) {
721                    titleView.setInputType(action.getEditInputType());
722                }
723                vh.mEditingMode = EDITING_TITLE;
724            } else if (vh.mActivatorView != null) {
725                onEditActivatorView(vh, action, editing);
726                vh.mEditingMode = EDITING_ACTIVATOR_VIEW;
727            }
728        } else {
729            if (titleView != null) {
730                titleView.setText(action.getTitle());
731            }
732            if (descriptionView != null) {
733                descriptionView.setText(action.getDescription());
734            }
735            if (vh.mEditingMode == EDITING_DESCRIPTION) {
736                if (descriptionView != null) {
737                    descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
738                            View.GONE : View.VISIBLE);
739                    descriptionView.setInputType(action.getDescriptionInputType());
740                }
741            } else if (vh.mEditingMode == EDITING_TITLE) {
742                if (titleView != null) {
743                    titleView.setInputType(action.getInputType());
744                }
745            } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) {
746                if (vh.mActivatorView != null) {
747                    onEditActivatorView(vh, action, editing);
748                }
749            }
750            vh.mEditingMode = EDITING_NONE;
751        }
752    }
753
754    /**
755     * Animates the view holder's view (or subviews thereof) when the action has had its focus
756     * state changed.
757     * @param vh The view holder associated with the relevant action.
758     * @param focused True if the action has become focused, false if it has lost focus.
759     */
760    public void onAnimateItemFocused(ViewHolder vh, boolean focused) {
761        // No animations for this, currently, because the animation is done on
762        // mSelectorView
763    }
764
765    /**
766     * Animates the view holder's view (or subviews thereof) when the action has had its press
767     * state changed.
768     * @param vh The view holder associated with the relevant action.
769     * @param pressed True if the action has been pressed, false if it has been unpressed.
770     */
771    public void onAnimateItemPressed(ViewHolder vh, boolean pressed) {
772        int attr = pressed ? R.attr.guidedActionPressedAnimation :
773                R.attr.guidedActionUnpressedAnimation;
774        createAnimator(vh.itemView, attr).start();
775    }
776
777    /**
778     * Resets the view holder's view to unpressed state.
779     * @param vh The view holder associated with the relevant action.
780     */
781    public void onAnimateItemPressedCancelled(ViewHolder vh) {
782        createAnimator(vh.itemView, R.attr.guidedActionUnpressedAnimation).end();
783    }
784
785    /**
786     * Animates the view holder's view (or subviews thereof) when the action has had its check state
787     * changed. Default implementation calls setChecked() if {@link ViewHolder#getCheckmarkView()}
788     * is instance of {@link Checkable}.
789     *
790     * @param vh The view holder associated with the relevant action.
791     * @param checked True if the action has become checked, false if it has become unchecked.
792     * @see #onBindCheckMarkView(ViewHolder, GuidedAction)
793     */
794    public void onAnimateItemChecked(ViewHolder vh, boolean checked) {
795        if (vh.mCheckmarkView instanceof Checkable) {
796            ((Checkable) vh.mCheckmarkView).setChecked(checked);
797        }
798    }
799
800    /**
801     * Sets states of check mark view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}
802     * when action's checkset Id is other than {@link GuidedAction#NO_CHECK_SET}. Default
803     * implementation assigns drawable loaded from theme attribute
804     * {@link android.R.attr#listChoiceIndicatorMultiple} for checkbox or
805     * {@link android.R.attr#listChoiceIndicatorSingle} for radio button. Subclass rarely needs
806     * override the method, instead app can provide its own drawable that supports transition
807     * animations, change theme attributes {@link android.R.attr#listChoiceIndicatorMultiple} and
808     * {@link android.R.attr#listChoiceIndicatorSingle} in {android.support.v17.leanback.R.
809     * styleable#LeanbackGuidedStepTheme}.
810     *
811     * @param vh The view holder associated with the relevant action.
812     * @param action The GuidedAction object to bind to.
813     * @see #onAnimateItemChecked(ViewHolder, boolean)
814     */
815    public void onBindCheckMarkView(ViewHolder vh, GuidedAction action) {
816        if (action.getCheckSetId() != GuidedAction.NO_CHECK_SET) {
817            vh.mCheckmarkView.setVisibility(View.VISIBLE);
818            int attrId = action.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID ?
819                    android.R.attr.listChoiceIndicatorMultiple :
820                    android.R.attr.listChoiceIndicatorSingle;
821            final Context context = vh.mCheckmarkView.getContext();
822            Drawable drawable = null;
823            TypedValue typedValue = new TypedValue();
824            if (context.getTheme().resolveAttribute(attrId, typedValue, true)) {
825                drawable = ContextCompat.getDrawable(context, typedValue.resourceId);
826            }
827            vh.mCheckmarkView.setImageDrawable(drawable);
828            if (vh.mCheckmarkView instanceof Checkable) {
829                ((Checkable) vh.mCheckmarkView).setChecked(action.isChecked());
830            }
831        } else {
832            vh.mCheckmarkView.setVisibility(View.GONE);
833        }
834    }
835
836    /**
837     * Performs binding activator view value to action.  Default implementation supports
838     * GuidedDatePickerAction, subclass may override to add support of other views.
839     * @param vh ViewHolder of activator view.
840     * @param action GuidedAction to bind.
841     */
842    public void onBindActivatorView(ViewHolder vh, GuidedAction action) {
843        if (action instanceof GuidedDatePickerAction) {
844            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
845            DatePicker dateView = (DatePicker) vh.mActivatorView;
846            dateView.setDatePickerFormat(dateAction.getDatePickerFormat());
847            if (dateAction.getMinDate() != Long.MIN_VALUE) {
848                dateView.setMinDate(dateAction.getMinDate());
849            }
850            if (dateAction.getMaxDate() != Long.MAX_VALUE) {
851                dateView.setMaxDate(dateAction.getMaxDate());
852            }
853            Calendar c = Calendar.getInstance();
854            c.setTimeInMillis(dateAction.getDate());
855            dateView.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
856                    c.get(Calendar.DAY_OF_MONTH), false);
857        }
858    }
859
860    /**
861     * Performs updating GuidedAction from activator view.  Default implementation supports
862     * GuidedDatePickerAction, subclass may override to add support of other views.
863     * @param vh ViewHolder of activator view.
864     * @param action GuidedAction to update.
865     * @return True if value has been updated, false otherwise.
866     */
867    public boolean onUpdateActivatorView(ViewHolder vh, GuidedAction action) {
868        if (action instanceof GuidedDatePickerAction) {
869            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
870            DatePicker dateView = (DatePicker) vh.mActivatorView;
871            if (dateAction.getDate() != dateView.getDate()) {
872                dateAction.setDate(dateView.getDate());
873                return true;
874            }
875        }
876        return false;
877    }
878
879    /**
880     * Sets listener for reporting view being edited.
881     * @hide
882     */
883    public void setEditListener(EditListener listener) {
884        mEditListener = listener;
885    }
886
887    void onEditActivatorView(final ViewHolder vh, final GuidedAction action,
888            boolean editing) {
889        if (editing) {
890            vh.itemView.setFocusable(false);
891            vh.mActivatorView.requestFocus();
892            setExpandedViewHolder(vh);
893            vh.mActivatorView.setOnClickListener(new View.OnClickListener() {
894                @Override
895                public void onClick(View v) {
896                    if (!isInExpandTransition()) {
897                        setEditingMode(vh, action, false);
898                    }
899                }
900            });
901        } else {
902            if (onUpdateActivatorView(vh, action)) {
903                if (mEditListener != null) {
904                    mEditListener.onGuidedActionEditedAndProceed(action);
905                }
906            }
907            vh.itemView.setFocusable(true);
908            vh.itemView.requestFocus();
909            setExpandedViewHolder(null);
910            vh.mActivatorView.setOnClickListener(null);
911            vh.mActivatorView.setClickable(false);
912        }
913    }
914
915    /**
916     * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}.
917     * Subclass may override.
918     *
919     * @param vh The view holder associated with the relevant action.
920     * @param action The GuidedAction object to bind to.
921     */
922    public void onBindChevronView(ViewHolder vh, GuidedAction action) {
923        final boolean hasNext = action.hasNext();
924        final boolean hasSubActions = action.hasSubActions();
925        if (hasNext || hasSubActions) {
926            vh.mChevronView.setVisibility(View.VISIBLE);
927            vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
928                    mDisabledChevronAlpha);
929            if (hasNext) {
930                float r = mMainView != null
931                        && mMainView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? 180f : 0f;
932                vh.mChevronView.setRotation(r);
933            } else if (action == mExpandedAction) {
934                vh.mChevronView.setRotation(270);
935            } else {
936                vh.mChevronView.setRotation(90);
937            }
938        } else {
939            vh.mChevronView.setVisibility(View.GONE);
940
941        }
942    }
943
944    /**
945     * Expands or collapse the sub actions list view.
946     * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and
947     * hide the other items in main list.  When null, collapse the sub actions list.
948     */
949    public void setExpandedViewHolder(ViewHolder avh) {
950        if (isInExpandTransition()) {
951            return;
952        }
953        if (isExpandTransitionSupported()) {
954            startExpandedTransition(avh);
955        } else {
956            onUpdateExpandedViewHolder(avh);
957        }
958    }
959
960    /**
961     * Returns true if it is running an expanding or collapsing transition, false otherwise.
962     * @return True if it is running an expanding or collapsing transition, false otherwise.
963     */
964    public boolean isInExpandTransition() {
965        return mExpandTransition != null;
966    }
967
968    /**
969     * Returns if expand/collapse animation is supported.  When this method returns true,
970     * {@link #startExpandedTransition(ViewHolder)} will be used.  When this method returns false,
971     * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called.
972     * @return True if it is running an expanding or collapsing transition, false otherwise.
973     */
974    public boolean isExpandTransitionSupported() {
975        return VERSION.SDK_INT >= 21;
976    }
977
978    /**
979     * Start transition to expand or collapse GuidedActionStylist.
980     * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
981     * the GuidedActionStylist will collapse sub actions.
982     */
983    public void startExpandedTransition(ViewHolder avh) {
984        ViewHolder focusAvh = null; // expand / collapse view holder
985        final int count = mActionsGridView.getChildCount();
986        for (int i = 0; i < count; i++) {
987            ViewHolder vh = (ViewHolder) mActionsGridView
988                    .getChildViewHolder(mActionsGridView.getChildAt(i));
989            if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) {
990                // going to collapse this one.
991                focusAvh = vh;
992                break;
993            } else if (avh != null && vh.getAction() == avh.getAction()) {
994                // going to expand this one.
995                focusAvh = vh;
996                break;
997            }
998        }
999        if (focusAvh == null) {
1000            // huh?
1001            onUpdateExpandedViewHolder(avh);
1002            return;
1003        }
1004        boolean isSubActionTransition = focusAvh.getAction().hasSubActions();
1005        Object set = TransitionHelper.createTransitionSet(false);
1006        float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight() :
1007                focusAvh.itemView.getHeight() * 0.5f;
1008        Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM,
1009                slideDistance);
1010        Object changeFocusItemTransform = TransitionHelper.createChangeTransform();
1011        Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false);
1012        Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
1013                TransitionHelper.FADE_OUT);
1014        Object changeGridBounds = TransitionHelper.createChangeBounds(false);
1015        if (avh == null) {
1016            TransitionHelper.setStartDelay(slideAndFade, 150);
1017            TransitionHelper.setStartDelay(changeFocusItemTransform, 100);
1018            TransitionHelper.setStartDelay(changeFocusItemBounds, 100);
1019        } else {
1020            TransitionHelper.setStartDelay(fade, 100);
1021            TransitionHelper.setStartDelay(changeGridBounds, 100);
1022            TransitionHelper.setStartDelay(changeFocusItemTransform, 50);
1023            TransitionHelper.setStartDelay(changeFocusItemBounds, 50);
1024        }
1025        for (int i = 0; i < count; i++) {
1026            ViewHolder vh = (ViewHolder) mActionsGridView
1027                    .getChildViewHolder(mActionsGridView.getChildAt(i));
1028            if (vh == focusAvh) {
1029                // going to expand/collapse this one.
1030                if (isSubActionTransition) {
1031                    TransitionHelper.include(changeFocusItemTransform, vh.itemView);
1032                    TransitionHelper.include(changeFocusItemBounds, vh.itemView);
1033                }
1034            } else {
1035                // going to slide this item to top / bottom.
1036                TransitionHelper.include(slideAndFade, vh.itemView);
1037                TransitionHelper.exclude(fade, vh.itemView, true);
1038            }
1039        }
1040        TransitionHelper.include(changeGridBounds, mSubActionsGridView);
1041        TransitionHelper.addTransition(set, slideAndFade);
1042        // note that we don't run ChangeBounds for activating view due to the rounding problem
1043        // of multiple level views ChangeBounds animation causing vertical jittering.
1044        if (isSubActionTransition) {
1045            TransitionHelper.addTransition(set, changeFocusItemTransform);
1046            TransitionHelper.addTransition(set, changeFocusItemBounds);
1047        }
1048        TransitionHelper.addTransition(set, fade);
1049        TransitionHelper.addTransition(set, changeGridBounds);
1050        mExpandTransition = set;
1051        TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() {
1052            @Override
1053            public void onTransitionEnd(Object transition) {
1054                mExpandTransition = null;
1055            }
1056        });
1057        if (avh != null && mSubActionsGridView.getTop() != avh.itemView.getTop()) {
1058            // For expanding, set the initial position of subActionsGridView before running
1059            // a ChangeBounds on it.
1060            final ViewHolder toUpdate = avh;
1061            mSubActionsGridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
1062                @Override
1063                public void onLayoutChange(View v, int left, int top, int right, int bottom,
1064                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
1065                    if (mSubActionsGridView == null) {
1066                        return;
1067                    }
1068                    mSubActionsGridView.removeOnLayoutChangeListener(this);
1069                    mMainView.post(new Runnable() {
1070                        public void run() {
1071                            if (mMainView == null) {
1072                                return;
1073                            }
1074                            TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
1075                            onUpdateExpandedViewHolder(toUpdate);
1076                        }
1077                    });
1078                }
1079            });
1080            ViewGroup.MarginLayoutParams lp =
1081                    (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
1082            lp.topMargin = avh.itemView.getTop();
1083            lp.height = 0;
1084            mSubActionsGridView.setLayoutParams(lp);
1085            return;
1086        }
1087        TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
1088        onUpdateExpandedViewHolder(avh);
1089    }
1090
1091    /**
1092     * @return True if sub actions list is expanded.
1093     */
1094    public boolean isSubActionsExpanded() {
1095        return mExpandedAction != null;
1096    }
1097
1098    /**
1099     * @return Current expanded GuidedAction or null if not expanded.
1100     */
1101    public GuidedAction getExpandedAction() {
1102        return mExpandedAction;
1103    }
1104
1105    /**
1106     * Expand or collapse GuidedActionStylist.
1107     * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
1108     * the GuidedActionStylist will collapse sub actions.
1109     */
1110    public void onUpdateExpandedViewHolder(ViewHolder avh) {
1111
1112        // Note about setting the prune child flag back & forth here: without this, the actions that
1113        // go off the screen from the top or bottom become invisible forever. This is because once
1114        // an action is expanded, it takes more space which in turn kicks out some other actions
1115        // off of the screen. Once, this action is collapsed (after the second click) and the
1116        // visibility flag is set back to true for all existing actions,
1117        // the off-the-screen actions are pruned from the view, thus
1118        // could not be accessed, had we not disabled pruning prior to this.
1119        if (avh == null) {
1120            mExpandedAction = null;
1121            mActionsGridView.setPruneChild(true);
1122        } else if (avh.getAction() != mExpandedAction) {
1123            mExpandedAction = avh.getAction();
1124            mActionsGridView.setPruneChild(false);
1125        }
1126        // In expanding mode, notifyItemChange on expanded item will reset the translationY by
1127        // the default ItemAnimator.  So disable ItemAnimation in expanding mode.
1128        mActionsGridView.setAnimateChildLayout(false);
1129        final int count = mActionsGridView.getChildCount();
1130        for (int i = 0; i < count; i++) {
1131            ViewHolder vh = (ViewHolder) mActionsGridView
1132                    .getChildViewHolder(mActionsGridView.getChildAt(i));
1133            updateChevronAndVisibility(vh);
1134        }
1135        if (mSubActionsGridView != null) {
1136            if (avh != null && avh.getAction().hasSubActions()) {
1137                ViewGroup.MarginLayoutParams lp =
1138                        (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
1139                lp.topMargin = avh.itemView.getTop();
1140                lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
1141                mSubActionsGridView.setLayoutParams(lp);
1142                mSubActionsGridView.setVisibility(View.VISIBLE);
1143                mSubActionsGridView.requestFocus();
1144                mSubActionsGridView.setSelectedPosition(0);
1145                ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
1146                        .setActions(avh.getAction().getSubActions());
1147            } else if (mSubActionsGridView.getVisibility() == View.VISIBLE) {
1148                mSubActionsGridView.setVisibility(View.INVISIBLE);
1149                ViewGroup.MarginLayoutParams lp =
1150                        (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
1151                lp.height = 0;
1152                mSubActionsGridView.setLayoutParams(lp);
1153                ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
1154                        .setActions(Collections.EMPTY_LIST);
1155                mActionsGridView.requestFocus();
1156            }
1157        }
1158    }
1159
1160    private void updateChevronAndVisibility(ViewHolder vh) {
1161        if (!vh.isSubAction()) {
1162            if (mExpandedAction == null) {
1163                vh.itemView.setVisibility(View.VISIBLE);
1164                vh.itemView.setTranslationY(0);
1165                if (vh.mActivatorView != null) {
1166                    vh.setActivated(false);
1167                }
1168            } else if (vh.getAction() == mExpandedAction) {
1169                vh.itemView.setVisibility(View.VISIBLE);
1170                if (vh.getAction().hasSubActions()) {
1171                    vh.itemView.setTranslationY(- vh.itemView.getHeight());
1172                } else if (vh.mActivatorView != null) {
1173                    vh.itemView.setTranslationY(0);
1174                    vh.setActivated(true);
1175                }
1176            } else {
1177                vh.itemView.setVisibility(View.INVISIBLE);
1178                vh.itemView.setTranslationY(0);
1179            }
1180        }
1181        if (vh.mChevronView != null) {
1182            onBindChevronView(vh, vh.getAction());
1183        }
1184    }
1185
1186    /*
1187     * ==========================================
1188     * FragmentAnimationProvider overrides
1189     * ==========================================
1190     */
1191
1192    /**
1193     * {@inheritDoc}
1194     */
1195    @Override
1196    public void onImeAppearing(@NonNull List<Animator> animators) {
1197        animators.add(createAnimator(mContentView, R.attr.guidedStepImeAppearingAnimation));
1198    }
1199
1200    /**
1201     * {@inheritDoc}
1202     */
1203    @Override
1204    public void onImeDisappearing(@NonNull List<Animator> animators) {
1205        animators.add(createAnimator(mContentView, R.attr.guidedStepImeDisappearingAnimation));
1206    }
1207
1208    /*
1209     * ==========================================
1210     * Private methods
1211     * ==========================================
1212     */
1213
1214    private float getFloat(Context ctx, TypedValue typedValue, int attrId) {
1215        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
1216        // Android resources don't have a native float type, so we have to use strings.
1217        return Float.valueOf(ctx.getResources().getString(typedValue.resourceId));
1218    }
1219
1220    private int getInteger(Context ctx, TypedValue typedValue, int attrId) {
1221        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
1222        return ctx.getResources().getInteger(typedValue.resourceId);
1223    }
1224
1225    private int getDimension(Context ctx, TypedValue typedValue, int attrId) {
1226        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
1227        return ctx.getResources().getDimensionPixelSize(typedValue.resourceId);
1228    }
1229
1230    private static Animator createAnimator(View v, int attrId) {
1231        Context ctx = v.getContext();
1232        TypedValue typedValue = new TypedValue();
1233        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
1234        Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
1235        animator.setTarget(v);
1236        return animator;
1237    }
1238
1239    private boolean setIcon(final ImageView iconView, GuidedAction action) {
1240        Drawable icon = null;
1241        if (iconView != null) {
1242            Context context = iconView.getContext();
1243            icon = action.getIcon();
1244            if (icon != null) {
1245                // setImageDrawable resets the drawable's level unless we set the view level first.
1246                iconView.setImageLevel(icon.getLevel());
1247                iconView.setImageDrawable(icon);
1248                iconView.setVisibility(View.VISIBLE);
1249            } else {
1250                iconView.setVisibility(View.GONE);
1251            }
1252        }
1253        return icon != null;
1254    }
1255
1256    /**
1257     * @return the max height in pixels the description can be such that the
1258     *         action nicely takes up the entire screen.
1259     */
1260    private int getDescriptionMaxHeight(Context context, TextView title) {
1261        // The 2 multiplier on the title height calculation is a
1262        // conservative estimate for font padding which can not be
1263        // calculated at this stage since the view hasn't been rendered yet.
1264        return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight());
1265    }
1266
1267}
1268