public class

GuidedActionsStylist

extends java.lang.Object

implements FragmentAnimationProvider

 java.lang.Object

↳androidx.leanback.widget.GuidedActionsStylist

Gradle dependencies

compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'

  • groupId: androidx.leanback
  • artifactId: leanback
  • version: 1.2.0-alpha04

Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.leanback:leanback com.android.support:leanback-v17

Androidx class mapping:

androidx.leanback.widget.GuidedActionsStylist android.support.v17.leanback.widget.GuidedActionsStylist

Overview

GuidedActionsStylist is used within a GuidedStepFragment to supply the right-side panel where users can take actions. It consists of a container for the list of actions, and a stationary selector view that indicates visually the location of focus. GuidedActionsStylist has two different layouts: default is for normal actions including text, radio, checkbox, DatePicker, etc, the other when GuidedActionsStylist.setAsButtonActions() is called is recommended for button actions such as "yes", "no".

Many aspects of the base GuidedActionsStylist can be customized through theming; see the theme attributes below. Note that these attributes are not set on individual elements in layout XML, but instead would be set in a custom theme. See Styles and Themes for more information.

If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to override the GuidedActionsStylist.onProvideLayoutId() method to change the layout used to display the list container and selector; override GuidedActionsStylist.onProvideItemLayoutId(int) and GuidedActionsStylist.getItemViewType(GuidedAction) method to change the layout used to display each action.

To support a "click to activate" view similar to DatePicker, app needs:

Note: If an alternate list layout is provided, the following view IDs must be supplied:

These view IDs must be present in order for the stylist to function. The list ID must correspond to a VerticalGridView or subclass.

If an alternate item layout is provided, the following view IDs should be used to refer to base elements:

These view IDs are allowed to be missing, in which case the corresponding views in GuidedActionsStylist.ViewHolder will be null.

In order to support editable actions, the view associated with guidedactions_item_title should be a subclass of , and should satisfy the ImeKeyMonitor interface and GuidedActionAutofillSupport interface.

Summary

Fields
public static final intVIEW_TYPE_DATE_PICKER

ViewType for DatePicker.

public static final intVIEW_TYPE_DEFAULT

Default viewType that associated with default layout Id for the action item.

Constructors
publicGuidedActionsStylist()

Methods
public voidcollapseAction(boolean withTransition)

Collapse expanded action.

public voidexpandAction(GuidedAction action, boolean withTransition)

Expand an action.

public VerticalGridViewgetActionsGridView()

Returns the VerticalGridView that displays the list of GuidedActions.

public GuidedActiongetExpandedAction()

public intgetItemViewType(GuidedAction action)

Return view type of action, each different type can have differently associated layout Id.

public VerticalGridViewgetSubActionsGridView()

Returns the VerticalGridView that displays the sub actions list of an expanded action.

public final booleanisBackKeyToCollapseActivatorView()

public final booleanisBackKeyToCollapseSubActions()

public booleanisButtonActions()

Returns true if it is button actions list, false for normal actions list.

public booleanisExpanded()

public booleanisExpandTransitionSupported()

Returns if expand/collapse animation is supported.

public booleanisInExpandTransition()

Returns true if it is running an expanding or collapsing transition, false otherwise.

public booleanisSubActionsExpanded()

public voidonAnimateItemChecked(GuidedActionsStylist.ViewHolder vh, boolean checked)

Animates the view holder's view (or subviews thereof) when the action has had its check state changed.

public voidonAnimateItemFocused(GuidedActionsStylist.ViewHolder vh, boolean focused)

Animates the view holder's view (or subviews thereof) when the action has had its focus state changed.

public voidonAnimateItemPressed(GuidedActionsStylist.ViewHolder vh, boolean pressed)

Animates the view holder's view (or subviews thereof) when the action has had its press state changed.

public voidonAnimateItemPressedCancelled(GuidedActionsStylist.ViewHolder vh)

Resets the view holder's view to unpressed state.

public voidonBindActivatorView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Performs binding activator view value to action.

public voidonBindCheckMarkView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Sets states of check mark view, called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction) when action's checkset Id is other than GuidedAction.NO_CHECK_SET.

public voidonBindChevronView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Sets states of chevron view, called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction).

public voidonBindViewHolder(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Binds a GuidedActionsStylist.ViewHolder to a particular GuidedAction.

public ViewonCreateView(LayoutInflater inflater, ViewGroup container)

Creates a view appropriate for displaying a list of GuidedActions, using the provided inflater and container.

public GuidedActionsStylist.ViewHolderonCreateViewHolder(ViewGroup parent)

Constructs a GuidedActionsStylist.ViewHolder capable of representing GuidedActions.

public GuidedActionsStylist.ViewHolderonCreateViewHolder(ViewGroup parent, int viewType)

Constructs a GuidedActionsStylist.ViewHolder capable of representing GuidedActions.

public voidonDestroyView()

Called when destroy the View created by GuidedActionsStylist.

protected voidonEditingModeChange(GuidedActionsStylist.ViewHolder vh, boolean editing, boolean withTransition)

Called when editing mode of an ViewHolder is changed.

protected voidonEditingModeChange(GuidedActionsStylist.ViewHolder vh, GuidedAction action, boolean editing)

public voidonImeAppearing(java.util.List<Animator> animators)

public voidonImeDisappearing(java.util.List<Animator> animators)

public intonProvideItemLayoutId()

Provides the resource ID of the layout defining the view for an individual guided actions.

public intonProvideItemLayoutId(int viewType)

Provides the resource ID of the layout defining the view for an individual guided actions.

public intonProvideLayoutId()

Provides the resource ID of the layout defining the host view for the list of guided actions.

public booleanonUpdateActivatorView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Performs updating GuidedAction from activator view.

public voidonUpdateExpandedViewHolder(GuidedActionsStylist.ViewHolder avh)

Expand or collapse GuidedActionStylist.

public voidopenInEditMode(GuidedAction action)

Switches action to edit mode and pops up the keyboard.

public voidsetAsButtonActions()

Choose the layout resource for button actions in GuidedActionsStylist.onProvideLayoutId().

public final voidsetBackKeyToCollapseActivatorView(boolean backToCollapse)

Enable or disable using BACK key to collapse GuidedAction with editable activator view.

public final voidsetBackKeyToCollapseSubActions(boolean backToCollapse)

Enable or disable using BACK key to collapse sub actions list.

public voidsetEditingMode(GuidedActionsStylist.ViewHolder vh, GuidedAction action, boolean editing)

public voidsetEditListener(GuidedActionAdapter.EditListener listener)

Sets listener for reporting view being edited.

public voidsetExpandedViewHolder(GuidedActionsStylist.ViewHolder avh)

Expands or collapse the sub actions list view with transition animation

protected voidsetupImeOptions(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction) to setup IME options.

public voidstartExpandedTransition(GuidedActionsStylist.ViewHolder avh)

Start transition to expand or collapse GuidedActionStylist.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int VIEW_TYPE_DEFAULT

Default viewType that associated with default layout Id for the action item.

See also: GuidedActionsStylist.getItemViewType(GuidedAction), GuidedActionsStylist.onProvideItemLayoutId(int), GuidedActionsStylist.onCreateViewHolder(ViewGroup, int)

public static final int VIEW_TYPE_DATE_PICKER

ViewType for DatePicker.

Constructors

public GuidedActionsStylist()

Methods

public View onCreateView(LayoutInflater inflater, ViewGroup container)

Creates a view appropriate for displaying a list of GuidedActions, using the provided inflater and container.

Note: Does not actually add the created view to the container; the caller should do this.

Parameters:

inflater: The layout inflater to be used when constructing the view.
container: The view group to be passed in the call to LayoutInflater.inflate.

Returns:

The view to be added to the caller's view hierarchy.

public void setAsButtonActions()

Choose the layout resource for button actions in GuidedActionsStylist.onProvideLayoutId().

public boolean isButtonActions()

Returns true if it is button actions list, false for normal actions list.

Returns:

True if it is button actions list, false for normal actions list.

public void onDestroyView()

Called when destroy the View created by GuidedActionsStylist.

public VerticalGridView getActionsGridView()

Returns the VerticalGridView that displays the list of GuidedActions.

Returns:

The VerticalGridView for this presenter.

public VerticalGridView getSubActionsGridView()

Returns the VerticalGridView that displays the sub actions list of an expanded action.

Returns:

The VerticalGridView that displays the sub actions list of an expanded action.

public int onProvideLayoutId()

Provides the resource ID of the layout defining the host view for the list of guided actions. Subclasses may override to provide their own customized layouts. The base implementation returns or if GuidedActionsStylist.isButtonActions() is true. If overridden, the substituted layout should contain matching IDs for any views that should be managed by the base class; this can be achieved by starting with a copy of the base layout file.

Returns:

The resource ID of the layout to be inflated to define the host view for the list of GuidedActions.

public int getItemViewType(GuidedAction action)

Return view type of action, each different type can have differently associated layout Id. Default implementation returns GuidedActionsStylist.VIEW_TYPE_DEFAULT.

Parameters:

action: The action object.

Returns:

View type that used in GuidedActionsStylist.onProvideItemLayoutId(int).

public int onProvideItemLayoutId()

Provides the resource ID of the layout defining the view for an individual guided actions. Subclasses may override to provide their own customized layouts. The base implementation returns . If overridden, the substituted layout should contain matching IDs for any views that should be managed by the base class; this can be achieved by starting with a copy of the base layout file. Note that in order for the item to support editing, the title view should both subclass and implement ImeKeyMonitor, GuidedActionAutofillSupport; see GuidedActionEditText. To support different types of Layouts, override GuidedActionsStylist.onProvideItemLayoutId(int).

Returns:

The resource ID of the layout to be inflated to define the view to display an individual GuidedAction.

public int onProvideItemLayoutId(int viewType)

Provides the resource ID of the layout defining the view for an individual guided actions. Subclasses may override to provide their own customized layouts. The base implementation supports:

  • {.
If overridden, the substituted layout should contain matching IDs for any views that should be managed by the base class; this can be achieved by starting with a copy of the base layout file. Note that in order for the item to support editing, the title view should both subclass and implement ImeKeyMonitor; see GuidedActionEditText.

Parameters:

viewType: View type returned by GuidedActionsStylist.getItemViewType(GuidedAction)

Returns:

The resource ID of the layout to be inflated to define the view to display an individual GuidedAction.

public GuidedActionsStylist.ViewHolder onCreateViewHolder(ViewGroup parent)

Constructs a GuidedActionsStylist.ViewHolder capable of representing GuidedActions. Subclasses may choose to return a subclass of ViewHolder. To support different view types, override GuidedActionsStylist.onCreateViewHolder(ViewGroup, int)

Note: Should not actually add the created view to the parent; the caller will do this.

Parameters:

parent: The view group to be used as the parent of the new view.

Returns:

The view to be added to the caller's view hierarchy.

public GuidedActionsStylist.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

Constructs a GuidedActionsStylist.ViewHolder capable of representing GuidedActions. Subclasses may choose to return a subclass of ViewHolder.

Note: Should not actually add the created view to the parent; the caller will do this.

Parameters:

parent: The view group to be used as the parent of the new view.
viewType: The viewType returned by GuidedActionsStylist.getItemViewType(GuidedAction)

Returns:

The view to be added to the caller's view hierarchy.

public void onBindViewHolder(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Binds a GuidedActionsStylist.ViewHolder to a particular GuidedAction.

Parameters:

vh: The view holder to be associated with the given action.
action: The guided action to be displayed by the view holder's view.

Returns:

The view to be added to the caller's view hierarchy.

public void openInEditMode(GuidedAction action)

Switches action to edit mode and pops up the keyboard.

protected void setupImeOptions(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction) to setup IME options. Default implementation assigns . Subclass may override.

Parameters:

vh: The view holder to be associated with the given action.
action: The guided action to be displayed by the view holder's view.

public void setEditingMode(GuidedActionsStylist.ViewHolder vh, GuidedAction action, boolean editing)

Deprecated: This method is for internal library use only and should not be called directly.

protected void onEditingModeChange(GuidedActionsStylist.ViewHolder vh, GuidedAction action, boolean editing)

Deprecated: Use GuidedActionsStylist.onEditingModeChange(GuidedActionsStylist.ViewHolder, boolean, boolean).

protected void onEditingModeChange(GuidedActionsStylist.ViewHolder vh, boolean editing, boolean withTransition)

Called when editing mode of an ViewHolder is changed. Subclass must call super.onEditingModeChange(vh,editing,withTransition).

Parameters:

vh: ViewHolder to change editing mode.
editing: True to enable editing, false to stop editing
withTransition: True to run expand transiiton, false otherwise.

public void onAnimateItemFocused(GuidedActionsStylist.ViewHolder vh, boolean focused)

Animates the view holder's view (or subviews thereof) when the action has had its focus state changed.

Parameters:

vh: The view holder associated with the relevant action.
focused: True if the action has become focused, false if it has lost focus.

public void onAnimateItemPressed(GuidedActionsStylist.ViewHolder vh, boolean pressed)

Animates the view holder's view (or subviews thereof) when the action has had its press state changed.

Parameters:

vh: The view holder associated with the relevant action.
pressed: True if the action has been pressed, false if it has been unpressed.

public void onAnimateItemPressedCancelled(GuidedActionsStylist.ViewHolder vh)

Resets the view holder's view to unpressed state.

Parameters:

vh: The view holder associated with the relevant action.

public void onAnimateItemChecked(GuidedActionsStylist.ViewHolder vh, boolean checked)

Animates the view holder's view (or subviews thereof) when the action has had its check state changed. Default implementation calls setChecked() if GuidedActionsStylist.ViewHolder.getCheckmarkView() is instance of .

Parameters:

vh: The view holder associated with the relevant action.
checked: True if the action has become checked, false if it has become unchecked.

See also: GuidedActionsStylist.onBindCheckMarkView(GuidedActionsStylist.ViewHolder, GuidedAction)

public void onBindCheckMarkView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Sets states of check mark view, called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction) when action's checkset Id is other than GuidedAction.NO_CHECK_SET. Default implementation assigns drawable loaded from theme attribute for checkbox or for radio button. Subclass rarely needs override the method, instead app can provide its own drawable that supports transition animations, change theme attributes and in {androidx.leanback.R. styleable#LeanbackGuidedStepTheme}.

Parameters:

vh: The view holder associated with the relevant action.
action: The GuidedAction object to bind to.

See also: GuidedActionsStylist.onAnimateItemChecked(GuidedActionsStylist.ViewHolder, boolean)

public void onBindActivatorView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Performs binding activator view value to action. Default implementation supports GuidedDatePickerAction, subclass may override to add support of other views.

Parameters:

vh: ViewHolder of activator view.
action: GuidedAction to bind.

public boolean onUpdateActivatorView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Performs updating GuidedAction from activator view. Default implementation supports GuidedDatePickerAction, subclass may override to add support of other views.

Parameters:

vh: ViewHolder of activator view.
action: GuidedAction to update.

Returns:

True if value has been updated, false otherwise.

public void setEditListener(GuidedActionAdapter.EditListener listener)

Sets listener for reporting view being edited.

public void onBindChevronView(GuidedActionsStylist.ViewHolder vh, GuidedAction action)

Sets states of chevron view, called by GuidedActionsStylist.onBindViewHolder(GuidedActionsStylist.ViewHolder, GuidedAction). Subclass may override.

Parameters:

vh: The view holder associated with the relevant action.
action: The GuidedAction object to bind to.

public void setExpandedViewHolder(GuidedActionsStylist.ViewHolder avh)

Deprecated: use GuidedActionsStylist.expandAction(GuidedAction, boolean) and GuidedActionsStylist.collapseAction(boolean)

Expands or collapse the sub actions list view with transition animation

Parameters:

avh: When not null, fill sub actions list of this ViewHolder into sub actions list and hide the other items in main list. When null, collapse the sub actions list.

public boolean isInExpandTransition()

Returns true if it is running an expanding or collapsing transition, false otherwise.

Returns:

True if it is running an expanding or collapsing transition, false otherwise.

public boolean isExpandTransitionSupported()

Returns if expand/collapse animation is supported. When this method returns true, GuidedActionsStylist.startExpandedTransition(GuidedActionsStylist.ViewHolder) will be used. When this method returns false, GuidedActionsStylist.onUpdateExpandedViewHolder(GuidedActionsStylist.ViewHolder) will be called.

Returns:

True if it is running an expanding or collapsing transition, false otherwise.

public void startExpandedTransition(GuidedActionsStylist.ViewHolder avh)

Deprecated: use GuidedActionsStylist.expandAction(GuidedAction, boolean) and GuidedActionsStylist.collapseAction(boolean)

Start transition to expand or collapse GuidedActionStylist.

Parameters:

avh: When not null, the GuidedActionStylist expands the sub actions of avh. When null the GuidedActionStylist will collapse sub actions.

public final void setBackKeyToCollapseSubActions(boolean backToCollapse)

Enable or disable using BACK key to collapse sub actions list. Default is enabled.

Parameters:

backToCollapse: True to enable using BACK key to collapse sub actions list, false to disable.

See also: GuidedAction.hasSubActions(), GuidedAction.getSubActions()

public final boolean isBackKeyToCollapseSubActions()

Returns:

True if using BACK key to collapse sub actions list, false otherwise. Default value is true.

See also: GuidedAction.hasSubActions(), GuidedAction.getSubActions()

public final void setBackKeyToCollapseActivatorView(boolean backToCollapse)

Enable or disable using BACK key to collapse GuidedAction with editable activator view. Default is enabled.

Parameters:

backToCollapse: True to enable using BACK key to collapse GuidedAction with editable activator view.

See also: GuidedAction.hasEditableActivatorView()

public final boolean isBackKeyToCollapseActivatorView()

Returns:

True if using BACK key to collapse GuidedAction with editable activator view, false otherwise. Default value is true.

See also: GuidedAction.hasEditableActivatorView()

public void expandAction(GuidedAction action, boolean withTransition)

Expand an action. Do nothing if it is in animation or there is action expanded.

Parameters:

action: Action to expand.
withTransition: True to run transition animation, false otherwsie.

public void collapseAction(boolean withTransition)

Collapse expanded action. Do nothing if it is in animation or there is no action expanded.

Parameters:

withTransition: True to run transition animation, false otherwsie.

public boolean isSubActionsExpanded()

Returns:

True if sub actions list is expanded.

public boolean isExpanded()

Returns:

True if there is GuidedActionsStylist.getExpandedAction() is not null, false otherwise.

public GuidedAction getExpandedAction()

Returns:

Current expanded GuidedAction or null if not expanded.

public void onUpdateExpandedViewHolder(GuidedActionsStylist.ViewHolder avh)

Expand or collapse GuidedActionStylist.

Parameters:

avh: When not null, the GuidedActionStylist expands the sub actions of avh. When null the GuidedActionStylist will collapse sub actions.

public void onImeAppearing(java.util.List<Animator> animators)

public void onImeDisappearing(java.util.List<Animator> animators)

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package androidx.leanback.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import static androidx.leanback.widget.GuidedAction.EDITING_ACTIVATOR_VIEW;
import static androidx.leanback.widget.GuidedAction.EDITING_DESCRIPTION;
import static androidx.leanback.widget.GuidedAction.EDITING_NONE;
import static androidx.leanback.widget.GuidedAction.EDITING_TITLE;

import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION;
import android.text.InputType;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.widget.Checkable;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.ContextCompat;
import androidx.leanback.R;
import androidx.leanback.transition.TransitionEpicenterCallback;
import androidx.leanback.transition.TransitionHelper;
import androidx.leanback.transition.TransitionListener;
import androidx.leanback.widget.GuidedActionAdapter.EditListener;
import androidx.leanback.widget.picker.DatePicker;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Calendar;
import java.util.Collections;
import java.util.List;

/**
 * GuidedActionsStylist is used within a {@link androidx.leanback.app.GuidedStepFragment}
 * to supply the right-side panel where users can take actions. It consists of a container for the
 * list of actions, and a stationary selector view that indicates visually the location of focus.
 * GuidedActionsStylist has two different layouts: default is for normal actions including text,
 * radio, checkbox, DatePicker, etc, the other when {@link #setAsButtonActions()} is called is
 * recommended for button actions such as "yes", "no".
 * <p>
 * Many aspects of the base GuidedActionsStylist can be customized through theming; see the
 * theme attributes below. Note that these attributes are not set on individual elements in layout
 * XML, but instead would be set in a custom theme. See
 * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>
 * for more information.
 * <p>
 * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to
 * override the {@link #onProvideLayoutId} method to change the layout used to display the
 * list container and selector; override {@link #onProvideItemLayoutId(int)} and
 * {@link #getItemViewType(GuidedAction)} method to change the layout used to display each action.
 * <p>
 * To support a "click to activate" view similar to DatePicker, app needs:
 * <ul>
 * <li> Override {@link #onProvideItemLayoutId(int)} and {@link #getItemViewType(GuidedAction)},
 * provides a layout id for the action.</li>
 * <li> The layout must include a widget with id "guidedactions_activator_item", the widget is
 * toggled edit mode by {@link View#setActivated(boolean)}.</li>
 * <li>
 *     Override {@link #onBindActivatorView(ViewHolder, GuidedAction)} to populate values into View.
 * </li>
 * <li>
 *     Override {@link #onUpdateActivatorView(ViewHolder, GuidedAction)} to update action.
 * </li>
 * </ul>
 * <p>
 * Note: If an alternate list layout is provided, the following view IDs must be supplied:
 * <ul>
 * <li>{@link androidx.leanback.R.id#guidedactions_list}</li>
 * </ul><p>
 * These view IDs must be present in order for the stylist to function. The list ID must correspond
 * to a {@link VerticalGridView} or subclass.
 * <p>
 * If an alternate item layout is provided, the following view IDs should be used to refer to base
 * elements:
 * <ul>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_content}</li>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_title}</li>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_description}</li>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_icon}</li>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_checkmark}</li>
 * <li>{@link androidx.leanback.R.id#guidedactions_item_chevron}</li>
 * </ul><p>
 * These view IDs are allowed to be missing, in which case the corresponding views in {@link
 * GuidedActionsStylist.ViewHolder} will be null.
 * <p>
 * In order to support editable actions, the view associated with guidedactions_item_title should
 * be a subclass of {@link android.widget.EditText}, and should satisfy the {@link
 * ImeKeyMonitor} interface and {@link GuidedActionAutofillSupport} interface.
 *
 * {@link androidx.leanback.R.attr#guidedStepImeAppearingAnimation}
 * {@link androidx.leanback.R.attr#guidedStepImeDisappearingAnimation}
 * {@link androidx.leanback.R.attr#guidedActionsSelectorDrawable}
 * {@link androidx.leanback.R.attr#guidedActionsListStyle}
 * {@link androidx.leanback.R.attr#guidedSubActionsListStyle}
 * {@link androidx.leanback.R.attr#guidedButtonActionsListStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemContainerStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemCheckmarkStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemIconStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemContentStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemTitleStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemDescriptionStyle}
 * {@link androidx.leanback.R.attr#guidedActionItemChevronStyle}
 * {@link androidx.leanback.R.attr#guidedActionPressedAnimation}
 * {@link androidx.leanback.R.attr#guidedActionUnpressedAnimation}
 * {@link androidx.leanback.R.attr#guidedActionEnabledChevronAlpha}
 * {@link androidx.leanback.R.attr#guidedActionDisabledChevronAlpha}
 * {@link androidx.leanback.R.attr#guidedActionTitleMinLines}
 * {@link androidx.leanback.R.attr#guidedActionTitleMaxLines}
 * {@link androidx.leanback.R.attr#guidedActionDescriptionMinLines}
 * {@link androidx.leanback.R.attr#guidedActionVerticalPadding}
 * @see android.R.attr#listChoiceIndicatorSingle
 * @see android.R.attr#listChoiceIndicatorMultiple
 * @see androidx.leanback.app.GuidedStepFragment
 * @see GuidedAction
 */
public class GuidedActionsStylist implements FragmentAnimationProvider {

    /**
     * Default viewType that associated with default layout Id for the action item.
     * @see #getItemViewType(GuidedAction)
     * @see #onProvideItemLayoutId(int)
     * @see #onCreateViewHolder(ViewGroup, int)
     */
    public static final int VIEW_TYPE_DEFAULT = 0;

    /**
     * ViewType for DatePicker.
     */
    public static final int VIEW_TYPE_DATE_PICKER = 1;

    final static ItemAlignmentFacet sGuidedActionItemAlignFacet;

    static {
        sGuidedActionItemAlignFacet = new ItemAlignmentFacet();
        ItemAlignmentFacet.ItemAlignmentDef alignedDef = new ItemAlignmentFacet.ItemAlignmentDef();
        alignedDef.setItemAlignmentViewId(R.id.guidedactions_item_title);
        alignedDef.setAlignedToTextViewBaseline(true);
        alignedDef.setItemAlignmentOffset(0);
        alignedDef.setItemAlignmentOffsetWithPadding(true);
        alignedDef.setItemAlignmentOffsetPercent(0);
        sGuidedActionItemAlignFacet.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]{alignedDef});
    }

    /**
     * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link
     * GuidedActionsStylist} may also wish to subclass this in order to add fields.
     * @see GuidedAction
     */
    public static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {

        GuidedAction mAction;
        private View mContentView;
        TextView mTitleView;
        TextView mDescriptionView;
        View mActivatorView;
        ImageView mIconView;
        ImageView mCheckmarkView;
        ImageView mChevronView;
        int mEditingMode = EDITING_NONE;
        private final boolean mIsSubAction;
        Animator mPressAnimator;

        final AccessibilityDelegate mDelegate = new AccessibilityDelegate() {
            @Override
            public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
                super.onInitializeAccessibilityEvent(host, event);
                event.setChecked(mAction != null && mAction.isChecked());
            }

            @Override
            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                info.setCheckable(
                        mAction != null && mAction.getCheckSetId() != GuidedAction.NO_CHECK_SET);
                info.setChecked(mAction != null && mAction.isChecked());
            }
        };

        /**
         * Constructs an ViewHolder and caches the relevant subviews.
         */
        public ViewHolder(@NonNull View v) {
            this(v, false);
        }

        /**
         * Constructs an ViewHolder for sub action and caches the relevant subviews.
         */
        public ViewHolder(@NonNull View v, boolean isSubAction) {
            super(v);

            mContentView = v.findViewById(R.id.guidedactions_item_content);
            mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title);
            mActivatorView = v.findViewById(R.id.guidedactions_activator_item);
            mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description);
            mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon);
            mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark);
            mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron);
            mIsSubAction = isSubAction;

            v.setAccessibilityDelegate(mDelegate);
        }

        /**
         * Returns the content view within this view holder's view, where title and description are
         * shown.
         */
        @Nullable
        public View getContentView() {
            return mContentView;
        }

        /**
         * Returns the title view within this view holder's view.
         */
        @Nullable
        public TextView getTitleView() {
            return mTitleView;
        }

        /**
         * Convenience method to return an editable version of the title, if possible,
         * or null if the title view isn't an EditText.
         */
        @Nullable
        public EditText getEditableTitleView() {
            return (mTitleView instanceof EditText) ? (EditText)mTitleView : null;
        }

        /**
         * Returns the description view within this view holder's view.
         */
        @Nullable
        public TextView getDescriptionView() {
            return mDescriptionView;
        }

        /**
         * Convenience method to return an editable version of the description, if possible,
         * or null if the description view isn't an EditText.
         */
        @Nullable
        public EditText getEditableDescriptionView() {
            return (mDescriptionView instanceof EditText) ? (EditText)mDescriptionView : null;
        }

        /**
         * Returns the icon view within this view holder's view.
         */
        @Nullable
        public ImageView getIconView() {
            return mIconView;
        }

        /**
         * Returns the checkmark view within this view holder's view.
         */
        @Nullable
        public ImageView getCheckmarkView() {
            return mCheckmarkView;
        }

        /**
         * Returns the chevron view within this view holder's view.
         */
        @Nullable
        public ImageView getChevronView() {
            return mChevronView;
        }

        /**
         * Returns true if in editing title, description, or activator View, false otherwise.
         */
        public boolean isInEditing() {
            return mEditingMode != EDITING_NONE;
        }

        /**
         * Returns true if in editing title, description, so IME would be open.
         * @return True if in editing title, description, so IME would be open, false otherwise.
         */
        public boolean isInEditingText() {
            return mEditingMode == EDITING_TITLE || mEditingMode == EDITING_DESCRIPTION;
        }

        /**
         * Returns true if the TextView is in editing title, false otherwise.
         */
        public boolean isInEditingTitle() {
            return mEditingMode == EDITING_TITLE;
        }

        /**
         * Returns true if the TextView is in editing description, false otherwise.
         */
        public boolean isInEditingDescription() {
            return mEditingMode == EDITING_DESCRIPTION;
        }

        /**
         * Returns true if is in editing activator view with id guidedactions_activator_item, false
         * otherwise.
         */
        public boolean isInEditingActivatorView() {
            return mEditingMode == EDITING_ACTIVATOR_VIEW;
        }

        /**
         * @return Current editing title view or description view or activator view or null if not
         * in editing.
         */
        @Nullable
        public View getEditingView() {
            switch(mEditingMode) {
            case EDITING_TITLE:
                return mTitleView;
            case EDITING_DESCRIPTION:
                return mDescriptionView;
            case EDITING_ACTIVATOR_VIEW:
                return mActivatorView;
            case EDITING_NONE:
            default:
                return null;
            }
        }

        /**
         * @return True if bound action is inside {@link GuidedAction#getSubActions()}, false
         * otherwise.
         */
        public boolean isSubAction() {
            return mIsSubAction;
        }

        /**
         * @return Currently bound action.
         */
        @Nullable
        public GuidedAction getAction() {
            return mAction;
        }

        void setActivated(boolean activated) {
            mActivatorView.setActivated(activated);
            if (itemView instanceof GuidedActionItemContainer) {
                ((GuidedActionItemContainer) itemView).setFocusOutAllowed(!activated);
            }
        }

        @Nullable
        @Override
        public Object getFacet(@NonNull Class<?> facetClass) {
            if (facetClass == ItemAlignmentFacet.class) {
                return sGuidedActionItemAlignFacet;
            }
            return null;
        }

        void press(boolean pressed) {
            if (mPressAnimator != null) {
                mPressAnimator.cancel();
                mPressAnimator = null;
            }
            final int themeAttrId = pressed ? R.attr.guidedActionPressedAnimation :
                    R.attr.guidedActionUnpressedAnimation;
            Context ctx = itemView.getContext();
            TypedValue typedValue = new TypedValue();
            if (ctx.getTheme().resolveAttribute(themeAttrId, typedValue, true)) {
                mPressAnimator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
                mPressAnimator.setTarget(itemView);
                mPressAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mPressAnimator = null;
                    }
                });
                mPressAnimator.start();
            }
        }
    }

    private static final String TAG = "GuidedActionsStylist";

    ViewGroup mMainView;
    private VerticalGridView mActionsGridView;
    VerticalGridView mSubActionsGridView;
    private View mSubActionsBackground;
    private View mContentView;
    private boolean mButtonActions;

    // Cached values from resources
    private float mEnabledTextAlpha;
    private float mDisabledTextAlpha;
    private float mEnabledDescriptionAlpha;
    private float mDisabledDescriptionAlpha;
    private float mEnabledChevronAlpha;
    private float mDisabledChevronAlpha;
    private int mTitleMinLines;
    private int mTitleMaxLines;
    private int mDescriptionMinLines;
    private int mVerticalPadding;
    private int mDisplayHeight;

    private EditListener mEditListener;

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    GuidedAction mExpandedAction = null;
    Object mExpandTransition;
    private boolean mBackToCollapseSubActions = true;
    private boolean mBackToCollapseActivatorView = true;

    private float mKeyLinePercent;

    /**
     * Creates a view appropriate for displaying a list of GuidedActions, using the provided
     * inflater and container.
     * <p>
     * <i>Note: Does not actually add the created view to the container; the caller should do
     * this.</i>
     * @param inflater The layout inflater to be used when constructing the view.
     * @param container The view group to be passed in the call to
     * <code>LayoutInflater.inflate</code>.
     * @return The view to be added to the caller's view hierarchy.
     */
    @NonNull
    @SuppressWarnings("deprecation") /* defaultDisplay */
    public View onCreateView(@NonNull LayoutInflater inflater, final @NonNull ViewGroup container) {
        TypedArray ta = inflater.getContext().getTheme().obtainStyledAttributes(
                R.styleable.LeanbackGuidedStepTheme);
        float keylinePercent = ta.getFloat(R.styleable.LeanbackGuidedStepTheme_guidedStepKeyline,
                40);
        mMainView = (ViewGroup) inflater.inflate(onProvideLayoutId(), container, false);
        mContentView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_content2 :
                R.id.guidedactions_content);
        if (mMainView instanceof VerticalGridView) {
            mActionsGridView = (VerticalGridView) mMainView;
        } else {
            mActionsGridView = (VerticalGridView) mMainView.findViewById(mButtonActions
                    ? R.id.guidedactions_list2 : R.id.guidedactions_list);
            if (mActionsGridView == null) {
                throw new IllegalStateException("No ListView exists.");
            }
            mActionsGridView.setWindowAlignmentOffsetPercent(keylinePercent);
            mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
            if (!mButtonActions) {
                mSubActionsGridView = (VerticalGridView) mMainView.findViewById(
                        R.id.guidedactions_sub_list);
                mSubActionsBackground = mMainView.findViewById(
                        R.id.guidedactions_sub_list_background);
            }
        }
        mActionsGridView.setFocusable(false);
        mActionsGridView.setFocusableInTouchMode(false);

        // Cache widths, chevron alpha values, max and min text lines, etc
        Context ctx = mMainView.getContext();
        TypedValue val = new TypedValue();
        mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha);
        mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha);
        mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines);
        mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines);
        mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines);
        mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding);
        mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay().getHeight();

        mEnabledTextAlpha = getFloatValue(ctx.getResources(), val, R.dimen
                .lb_guidedactions_item_unselected_text_alpha);
        mDisabledTextAlpha = getFloatValue(ctx.getResources(), val, R.dimen
                .lb_guidedactions_item_disabled_text_alpha);
        mEnabledDescriptionAlpha = getFloatValue(ctx.getResources(), val, R.dimen
                .lb_guidedactions_item_unselected_description_text_alpha);
        mDisabledDescriptionAlpha = getFloatValue(ctx.getResources(), val, R.dimen
                .lb_guidedactions_item_disabled_description_text_alpha);

        mKeyLinePercent = GuidanceStylingRelativeLayout.getKeyLinePercent(ctx);
        if (mContentView instanceof GuidedActionsRelativeLayout) {
            ((GuidedActionsRelativeLayout) mContentView).setInterceptKeyEventListener(
                    new GuidedActionsRelativeLayout.InterceptKeyEventListener() {
                        @Override
                        public boolean onInterceptKeyEvent(KeyEvent event) {
                            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                                    && event.getAction() == KeyEvent.ACTION_UP
                                    && mExpandedAction != null) {
                                if ((mExpandedAction.hasSubActions()
                                        && isBackKeyToCollapseSubActions())
                                        || (mExpandedAction.hasEditableActivatorView()
                                        && isBackKeyToCollapseActivatorView())) {
                                    collapseAction(true);
                                    return true;
                                }
                            }
                            return false;
                        }
                    }
            );
        }
        return mMainView;
    }

    /**
     * Choose the layout resource for button actions in {@link #onProvideLayoutId()}.
     */
    public void setAsButtonActions() {
        if (mMainView != null) {
            throw new IllegalStateException("setAsButtonActions() must be called before creating "
                    + "views");
        }
        mButtonActions = true;
    }

    /**
     * Returns true if it is button actions list, false for normal actions list.
     * @return True if it is button actions list, false for normal actions list.
     */
    public boolean isButtonActions() {
        return mButtonActions;
    }

    /**
     * Called when destroy the View created by GuidedActionsStylist.
     */
    public void onDestroyView() {
        mExpandedAction = null;
        mExpandTransition = null;
        mActionsGridView = null;
        mSubActionsGridView = null;
        mSubActionsBackground = null;
        mContentView = null;
        mMainView = null;
    }

    /**
     * Returns the VerticalGridView that displays the list of GuidedActions.
     * @return The VerticalGridView for this presenter.
     */
    @Nullable
    public VerticalGridView getActionsGridView() {
        return mActionsGridView;
    }

    /**
     * Returns the VerticalGridView that displays the sub actions list of an expanded action.
     * @return The VerticalGridView that displays the sub actions list of an expanded action.
     */
    @Nullable
    public VerticalGridView getSubActionsGridView() {
        return mSubActionsGridView;
    }

    /**
     * Provides the resource ID of the layout defining the host view for the list of guided actions.
     * Subclasses may override to provide their own customized layouts. The base implementation
     * returns {@link androidx.leanback.R.layout#lb_guidedactions} or
     * {@link androidx.leanback.R.layout#lb_guidedbuttonactions} if
     * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain
     * matching IDs for any views that should be managed by the base class; this can be achieved by
     * starting with a copy of the base layout file.
     *
     * @return The resource ID of the layout to be inflated to define the host view for the list of
     *         GuidedActions.
     */
    public int onProvideLayoutId() {
        return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions;
    }

    /**
     * Return view type of action, each different type can have differently associated layout Id.
     * Default implementation returns {@link #VIEW_TYPE_DEFAULT}.
     * @param action  The action object.
     * @return View type that used in {@link #onProvideItemLayoutId(int)}.
     */
    public int getItemViewType(@NonNull GuidedAction action) {
        if (action instanceof GuidedDatePickerAction) {
            return VIEW_TYPE_DATE_PICKER;
        }
        return VIEW_TYPE_DEFAULT;
    }

    /**
     * Provides the resource ID of the layout defining the view for an individual guided actions.
     * Subclasses may override to provide their own customized layouts. The base implementation
     * returns {@link androidx.leanback.R.layout#lb_guidedactions_item}. If overridden,
     * the substituted layout should contain matching IDs for any views that should be managed by
     * the base class; this can be achieved by starting with a copy of the base layout file. Note
     * that in order for the item to support editing, the title view should both subclass {@link
     * android.widget.EditText} and implement {@link ImeKeyMonitor},
     * {@link GuidedActionAutofillSupport}; see {@link
     * GuidedActionEditText}.  To support different types of Layouts, override {@link
     * #onProvideItemLayoutId(int)}.
     * @return The resource ID of the layout to be inflated to define the view to display an
     * individual GuidedAction.
     */
    public int onProvideItemLayoutId() {
        return R.layout.lb_guidedactions_item;
    }

    /**
     * Provides the resource ID of the layout defining the view for an individual guided actions.
     * Subclasses may override to provide their own customized layouts. The base implementation
     * supports:
     * <ul>
     * <li>{@link androidx.leanback.R.layout#lb_guidedactions_item}</li>
     * <li>{{@link androidx.leanback.R.layout#lb_guidedactions_datepicker_item}.</li>
     * </ul>
     * If overridden, the substituted layout should contain matching IDs for any views that should
     * be managed by the base class; this can be achieved by starting with a copy of the base layout
     * file. Note that in order for the item to support editing, the title view should both subclass
     * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see
     * {@link GuidedActionEditText}.
     *
     * @param viewType View type returned by {@link #getItemViewType(GuidedAction)}
     * @return The resource ID of the layout to be inflated to define the view to display an
     *         individual GuidedAction.
     */
    public int onProvideItemLayoutId(int viewType) {
        if (viewType == VIEW_TYPE_DEFAULT) {
            return onProvideItemLayoutId();
        } else if (viewType == VIEW_TYPE_DATE_PICKER) {
            return R.layout.lb_guidedactions_datepicker_item;
        } else {
            throw new RuntimeException("ViewType " + viewType
                    + " not supported in GuidedActionsStylist");
        }
    }

    /**
     * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
     * may choose to return a subclass of ViewHolder.  To support different view types, override
     * {@link #onCreateViewHolder(ViewGroup, int)}
     * <p>
     * <i>Note: Should not actually add the created view to the parent; the caller will do
     * this.</i>
     * @param parent The view group to be used as the parent of the new view.
     * @return The view to be added to the caller's view hierarchy.
     */
    @NonNull
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View v = inflater.inflate(onProvideItemLayoutId(), parent, false);
        return new ViewHolder(v, parent == mSubActionsGridView);
    }

    /**
     * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
     * may choose to return a subclass of ViewHolder.
     * <p>
     * <i>Note: Should not actually add the created view to the parent; the caller will do
     * this.</i>
     * @param parent The view group to be used as the parent of the new view.
     * @param viewType The viewType returned by {@link #getItemViewType(GuidedAction)}
     * @return The view to be added to the caller's view hierarchy.
     */
    @NonNull
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_DEFAULT) {
            return onCreateViewHolder(parent);
        }
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false);
        return new ViewHolder(v, parent == mSubActionsGridView);
    }

    /**
     * Binds a {@link ViewHolder} to a particular {@link GuidedAction}.
     * @param vh The view holder to be associated with the given action.
     * @param action The guided action to be displayed by the view holder's view.
     * @return The view to be added to the caller's view hierarchy.
     */
    public void onBindViewHolder(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        vh.mAction = action;
        if (vh.mTitleView != null) {
            vh.mTitleView.setInputType(action.getInputType());
            vh.mTitleView.setText(action.getTitle());
            vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha);
            vh.mTitleView.setFocusable(false);
            vh.mTitleView.setClickable(false);
            vh.mTitleView.setLongClickable(false);
            if (Build.VERSION.SDK_INT >= 28) {
                if (action.isEditable()) {
                    Api26Impl.setAutofillHints(vh.mTitleView, action.getAutofillHints());
                } else {
                    Api26Impl.setAutofillHints(vh.mTitleView, (String[]) null);
                }
            } else if (VERSION.SDK_INT >= 26) {
                // disable autofill below P as dpad/keyboard is not supported
                Api26Impl.setImportantForAutofill(vh.mTitleView, View.IMPORTANT_FOR_AUTOFILL_NO);
            }
        }
        if (vh.mDescriptionView != null) {
            vh.mDescriptionView.setInputType(action.getDescriptionInputType());
            vh.mDescriptionView.setText(action.getDescription());
            vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription())
                    ? View.GONE : View.VISIBLE);
            vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha :
                mDisabledDescriptionAlpha);
            vh.mDescriptionView.setFocusable(false);
            vh.mDescriptionView.setClickable(false);
            vh.mDescriptionView.setLongClickable(false);
            if (Build.VERSION.SDK_INT >= 28) {
                if (action.isDescriptionEditable()) {
                    Api26Impl.setAutofillHints(vh.mDescriptionView, action.getAutofillHints());
                } else {
                    Api26Impl.setAutofillHints(vh.mDescriptionView, (String[]) null);
                }
            } else if (VERSION.SDK_INT >= 26) {
                // disable autofill below P as dpad/keyboard is not supported
                Api26Impl.setImportantForAutofill(vh.mTitleView, View.IMPORTANT_FOR_AUTOFILL_NO);
            }
        }
        // Clients might want the check mark view to be gone entirely, in which case, ignore it.
        if (vh.mCheckmarkView != null) {
            onBindCheckMarkView(vh, action);
        }
        setIcon(vh.mIconView, action);

        if (action.hasMultilineDescription()) {
            if (vh.mTitleView != null) {
                setMaxLines(vh.mTitleView, mTitleMaxLines);
                vh.mTitleView.setInputType(
                        vh.mTitleView.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
                if (vh.mDescriptionView != null) {
                    vh.mDescriptionView.setInputType(vh.mDescriptionView.getInputType()
                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
                    vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(vh.mTitleView));
                }
            }
        } else {
            if (vh.mTitleView != null) {
                setMaxLines(vh.mTitleView, mTitleMinLines);
            }
            if (vh.mDescriptionView != null) {
                setMaxLines(vh.mDescriptionView, mDescriptionMinLines);
            }
        }
        if (vh.mActivatorView != null) {
            onBindActivatorView(vh, action);
        }
        setEditingMode(vh, false /*editing*/, false /*withTransition*/);
        if (action.isFocusable()) {
            vh.itemView.setFocusable(true);
            ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
        } else {
            vh.itemView.setFocusable(false);
            ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        }
        setupImeOptions(vh, action);

        updateChevronAndVisibility(vh);
    }

    /**
     * Switches action to edit mode and pops up the keyboard.
     */
    public void openInEditMode(@NonNull GuidedAction action) {
        final GuidedActionAdapter guidedActionAdapter =
                (GuidedActionAdapter) getActionsGridView().getAdapter();
        int actionIndex = guidedActionAdapter.getActions().indexOf(action);
        if (actionIndex < 0 || !action.isEditable()) {
            return;
        }

        getActionsGridView().setSelectedPosition(actionIndex, new ViewHolderTask() {
            @Override
            public void run(@NonNull RecyclerView.ViewHolder viewHolder) {
                ViewHolder vh = (ViewHolder) viewHolder;
                guidedActionAdapter.mGroup.openIme(guidedActionAdapter, vh);
            }
        });
    }

    private static void setMaxLines(TextView view, int maxLines) {
        // setSingleLine must be called before setMaxLines because it resets maximum to
        // Integer.MAX_VALUE.
        if (maxLines == 1) {
            view.setSingleLine(true);
        } else {
            view.setSingleLine(false);
            view.setMaxLines(maxLines);
        }
    }

    /**
     * Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options.  Default
     * implementation assigns {@link EditorInfo#IME_ACTION_DONE}.  Subclass may override.
     * @param vh The view holder to be associated with the given action.
     * @param action The guided action to be displayed by the view holder's view.
     */
    protected void setupImeOptions(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        setupNextImeOptions(vh.getEditableTitleView());
        setupNextImeOptions(vh.getEditableDescriptionView());
    }

    private void setupNextImeOptions(EditText edit) {
        if (edit != null) {
            edit.setImeOptions(EditorInfo.IME_ACTION_NEXT);
        }
    }

    /**
     * @deprecated This method is for internal library use only and should not
     *             be called directly.
     */
    @Deprecated
    public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) {
        if (editing != vh.isInEditing() && isInExpandTransition()) {
            onEditingModeChange(vh, action, editing);
        }
    }

    void setEditingMode(ViewHolder vh, boolean editing) {
        setEditingMode(vh, editing, true /*withTransition*/);
    }

    void setEditingMode(ViewHolder vh, boolean editing, boolean withTransition) {
        if (editing != vh.isInEditing() && !isInExpandTransition()) {
            onEditingModeChange(vh, editing, withTransition);
        }
    }

    /**
     * @deprecated Use {@link #onEditingModeChange(ViewHolder, boolean, boolean)}.
     */
    @Deprecated
    protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) {
    }

    /**
     * Called when editing mode of an ViewHolder is changed.  Subclass must call
     * <code>super.onEditingModeChange(vh,editing,withTransition)</code>.
     *
     * @param vh                ViewHolder to change editing mode.
     * @param editing           True to enable editing, false to stop editing
     * @param withTransition    True to run expand transiiton, false otherwise.
     */
    @CallSuper
    protected void onEditingModeChange(
            @NonNull ViewHolder vh,
            boolean editing,
            boolean withTransition) {
        GuidedAction action = vh.getAction();
        TextView titleView = vh.getTitleView();
        TextView descriptionView = vh.getDescriptionView();
        if (editing) {
            CharSequence editTitle = action.getEditTitle();
            if (titleView != null && editTitle != null) {
                titleView.setText(editTitle);
            }
            CharSequence editDescription = action.getEditDescription();
            if (descriptionView != null && editDescription != null) {
                descriptionView.setText(editDescription);
            }
            if (action.isDescriptionEditable()) {
                if (descriptionView != null) {
                    descriptionView.setVisibility(View.VISIBLE);
                    descriptionView.setInputType(action.getDescriptionEditInputType());
                    descriptionView.requestFocusFromTouch();
                }
                vh.mEditingMode = EDITING_DESCRIPTION;
            } else if (action.isEditable()){
                if (titleView != null) {
                    titleView.setInputType(action.getEditInputType());
                    titleView.requestFocusFromTouch();
                }
                vh.mEditingMode = EDITING_TITLE;
            } else if (vh.mActivatorView != null) {
                onEditActivatorView(vh, editing, withTransition);
                vh.mEditingMode = EDITING_ACTIVATOR_VIEW;
            }
        } else {
            if (titleView != null) {
                titleView.setText(action.getTitle());
            }
            if (descriptionView != null) {
                descriptionView.setText(action.getDescription());
            }
            if (vh.mEditingMode == EDITING_DESCRIPTION) {
                if (descriptionView != null) {
                    descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription())
                            ? View.GONE : View.VISIBLE);
                    descriptionView.setInputType(action.getDescriptionInputType());
                }
            } else if (vh.mEditingMode == EDITING_TITLE) {
                if (titleView != null) {
                    titleView.setInputType(action.getInputType());
                }
            } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) {
                if (vh.mActivatorView != null) {
                    onEditActivatorView(vh, editing, withTransition);
                }
            }
            vh.mEditingMode = EDITING_NONE;
        }
        // call deprecated method for backward compatible
        onEditingModeChange(vh, action, editing);
    }

    /**
     * Animates the view holder's view (or subviews thereof) when the action has had its focus
     * state changed.
     * @param vh The view holder associated with the relevant action.
     * @param focused True if the action has become focused, false if it has lost focus.
     */
    public void onAnimateItemFocused(@NonNull ViewHolder vh, boolean focused) {
        // No animations for this, currently, because the animation is done on
        // mSelectorView
    }

    /**
     * Animates the view holder's view (or subviews thereof) when the action has had its press
     * state changed.
     * @param vh The view holder associated with the relevant action.
     * @param pressed True if the action has been pressed, false if it has been unpressed.
     */
    public void onAnimateItemPressed(@NonNull ViewHolder vh, boolean pressed) {
        vh.press(pressed);
    }

    /**
     * Resets the view holder's view to unpressed state.
     * @param vh The view holder associated with the relevant action.
     */
    public void onAnimateItemPressedCancelled(@NonNull ViewHolder vh) {
        vh.press(false);
    }

    /**
     * Animates the view holder's view (or subviews thereof) when the action has had its check state
     * changed. Default implementation calls setChecked() if {@link ViewHolder#getCheckmarkView()}
     * is instance of {@link Checkable}.
     *
     * @param vh The view holder associated with the relevant action.
     * @param checked True if the action has become checked, false if it has become unchecked.
     * @see #onBindCheckMarkView(ViewHolder, GuidedAction)
     */
    public void onAnimateItemChecked(@NonNull ViewHolder vh, boolean checked) {
        if (vh.mCheckmarkView instanceof Checkable) {
            ((Checkable) vh.mCheckmarkView).setChecked(checked);
        }
    }

    /**
     * Sets states of check mark view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}
     * when action's checkset Id is other than {@link GuidedAction#NO_CHECK_SET}. Default
     * implementation assigns drawable loaded from theme attribute
     * {@link android.R.attr#listChoiceIndicatorMultiple} for checkbox or
     * {@link android.R.attr#listChoiceIndicatorSingle} for radio button. Subclass rarely needs
     * override the method, instead app can provide its own drawable that supports transition
     * animations, change theme attributes {@link android.R.attr#listChoiceIndicatorMultiple} and
     * {@link android.R.attr#listChoiceIndicatorSingle} in {androidx.leanback.R.
     * styleable#LeanbackGuidedStepTheme}.
     *
     * @param vh The view holder associated with the relevant action.
     * @param action The GuidedAction object to bind to.
     * @see #onAnimateItemChecked(ViewHolder, boolean)
     */
    public void onBindCheckMarkView(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        if (action.getCheckSetId() != GuidedAction.NO_CHECK_SET) {
            vh.mCheckmarkView.setVisibility(View.VISIBLE);
            int attrId = action.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID
                    ? android.R.attr.listChoiceIndicatorMultiple
                    : android.R.attr.listChoiceIndicatorSingle;
            final Context context = vh.mCheckmarkView.getContext();
            Drawable drawable = null;
            TypedValue typedValue = new TypedValue();
            if (context.getTheme().resolveAttribute(attrId, typedValue, true)) {
                drawable = ContextCompat.getDrawable(context, typedValue.resourceId);
            }
            vh.mCheckmarkView.setImageDrawable(drawable);
            if (vh.mCheckmarkView instanceof Checkable) {
                ((Checkable) vh.mCheckmarkView).setChecked(action.isChecked());
            }
        } else {
            vh.mCheckmarkView.setVisibility(View.GONE);
        }
    }

    /**
     * Performs binding activator view value to action.  Default implementation supports
     * GuidedDatePickerAction, subclass may override to add support of other views.
     * @param vh ViewHolder of activator view.
     * @param action GuidedAction to bind.
     */
    public void onBindActivatorView(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        if (action instanceof GuidedDatePickerAction) {
            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
            DatePicker dateView = (DatePicker) vh.mActivatorView;
            dateView.setDatePickerFormat(dateAction.getDatePickerFormat());
            if (dateAction.getMinDate() != Long.MIN_VALUE) {
                dateView.setMinDate(dateAction.getMinDate());
            }
            if (dateAction.getMaxDate() != Long.MAX_VALUE) {
                dateView.setMaxDate(dateAction.getMaxDate());
            }
            Calendar c = Calendar.getInstance();
            c.setTimeInMillis(dateAction.getDate());
            dateView.setDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
                    c.get(Calendar.DAY_OF_MONTH), false);
        }
    }

    /**
     * Performs updating GuidedAction from activator view.  Default implementation supports
     * GuidedDatePickerAction, subclass may override to add support of other views.
     * @param vh ViewHolder of activator view.
     * @param action GuidedAction to update.
     * @return True if value has been updated, false otherwise.
     */
    public boolean onUpdateActivatorView(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        if (action instanceof GuidedDatePickerAction) {
            GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
            DatePicker dateView = (DatePicker) vh.mActivatorView;
            if (dateAction.getDate() != dateView.getDate()) {
                dateAction.setDate(dateView.getDate());
                return true;
            }
        }
        return false;
    }

    /**
     * Sets listener for reporting view being edited.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public void setEditListener(@NonNull EditListener listener) {
        mEditListener = listener;
    }

    void onEditActivatorView(final ViewHolder vh, boolean editing, final boolean withTransition) {
        if (editing) {
            startExpanded(vh, withTransition);
            vh.itemView.setFocusable(false);
            vh.mActivatorView.requestFocus();
            vh.mActivatorView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!isInExpandTransition()) {
                        ((GuidedActionAdapter) getActionsGridView().getAdapter())
                                .performOnActionClick(vh);
                    }
                }
            });
        } else {
            if (onUpdateActivatorView(vh, vh.getAction())) {
                if (mEditListener != null) {
                    mEditListener.onGuidedActionEditedAndProceed(vh.getAction());
                }
            }
            vh.itemView.setFocusable(true);
            vh.itemView.requestFocus();
            startExpanded(null, withTransition);
            vh.mActivatorView.setOnClickListener(null);
            vh.mActivatorView.setClickable(false);
        }
    }

    /**
     * Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}.
     * Subclass may override.
     *
     * @param vh The view holder associated with the relevant action.
     * @param action The GuidedAction object to bind to.
     */
    public void onBindChevronView(@NonNull ViewHolder vh, @NonNull GuidedAction action) {
        final boolean hasNext = action.hasNext();
        final boolean hasSubActions = action.hasSubActions();
        if (hasNext || hasSubActions) {
            vh.mChevronView.setVisibility(View.VISIBLE);
            vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
                    mDisabledChevronAlpha);
            if (hasNext) {
                float r = mMainView != null
                        && mMainView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? 180f : 0f;
                vh.mChevronView.setRotation(r);
            } else if (action == mExpandedAction) {
                vh.mChevronView.setRotation(270);
            } else {
                vh.mChevronView.setRotation(90);
            }
        } else {
            vh.mChevronView.setVisibility(View.GONE);

        }
    }

    /**
     * Expands or collapse the sub actions list view with transition animation
     * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and
     * hide the other items in main list.  When null, collapse the sub actions list.
     * @deprecated use {@link #expandAction(GuidedAction, boolean)} and
     * {@link #collapseAction(boolean)}
     */
    @Deprecated
    public void setExpandedViewHolder(ViewHolder avh) {
        expandAction(avh == null ? null : avh.getAction(), isExpandTransitionSupported());
    }

    /**
     * Returns true if it is running an expanding or collapsing transition, false otherwise.
     * @return True if it is running an expanding or collapsing transition, false otherwise.
     */
    public boolean isInExpandTransition() {
        return mExpandTransition != null;
    }

    /**
     * Returns if expand/collapse animation is supported.  When this method returns true,
     * {@link #startExpandedTransition(ViewHolder)} will be used.  When this method returns false,
     * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called.
     * @return True if it is running an expanding or collapsing transition, false otherwise.
     */
    public boolean isExpandTransitionSupported() {
        return VERSION.SDK_INT >= 21;
    }

    /**
     * Start transition to expand or collapse GuidedActionStylist.
     * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
     * the GuidedActionStylist will collapse sub actions.
     * @deprecated use {@link #expandAction(GuidedAction, boolean)} and
     * {@link #collapseAction(boolean)}
     */
    @Deprecated
    public void startExpandedTransition(ViewHolder avh) {
        expandAction(avh == null ? null : avh.getAction(), isExpandTransitionSupported());
    }

    /**
     * Enable or disable using BACK key to collapse sub actions list. Default is enabled.
     *
     * @param backToCollapse True to enable using BACK key to collapse sub actions list, false
     *                       to disable.
     * @see GuidedAction#hasSubActions
     * @see GuidedAction#getSubActions
     */
    public final void setBackKeyToCollapseSubActions(boolean backToCollapse) {
        mBackToCollapseSubActions = backToCollapse;
    }

    /**
     * @return True if using BACK key to collapse sub actions list, false otherwise. Default value
     * is true.
     *
     * @see GuidedAction#hasSubActions
     * @see GuidedAction#getSubActions
     */
    public final boolean isBackKeyToCollapseSubActions() {
        return mBackToCollapseSubActions;
    }

    /**
     * Enable or disable using BACK key to collapse {@link GuidedAction} with editable activator
     * view. Default is enabled.
     *
     * @param backToCollapse True to enable using BACK key to collapse {@link GuidedAction} with
     *                       editable activator view.
     * @see GuidedAction#hasEditableActivatorView
     */
    public final void setBackKeyToCollapseActivatorView(boolean backToCollapse) {
        mBackToCollapseActivatorView = backToCollapse;
    }

    /**
     * @return True if using BACK key to collapse {@link GuidedAction} with editable activator
     * view, false otherwise. Default value is true.
     *
     * @see GuidedAction#hasEditableActivatorView
     */
    public final boolean isBackKeyToCollapseActivatorView() {
        return mBackToCollapseActivatorView;
    }

    /**
     * Expand an action. Do nothing if it is in animation or there is action expanded.
     *
     * @param action         Action to expand.
     * @param withTransition True to run transition animation, false otherwsie.
     */
    public void expandAction(@NonNull GuidedAction action, final boolean withTransition) {
        if (isInExpandTransition() || mExpandedAction != null) {
            return;
        }
        int actionPosition =
                ((GuidedActionAdapter) getActionsGridView().getAdapter()).indexOf(action);
        if (actionPosition < 0) {
            return;
        }
        boolean runTransition = isExpandTransitionSupported() && withTransition;
        if (!runTransition) {
            getActionsGridView().setSelectedPosition(actionPosition,
                    new ViewHolderTask() {
                        @Override
                        public void run(RecyclerView.ViewHolder vh) {
                            GuidedActionsStylist.ViewHolder avh =
                                    (GuidedActionsStylist.ViewHolder)vh;
                            if (avh.getAction().hasEditableActivatorView()) {
                                setEditingMode(avh, true /*editing*/, false /*withTransition*/);
                            } else {
                                onUpdateExpandedViewHolder(avh);
                            }
                        }
                    });
            if (action.hasSubActions()) {
                onUpdateSubActionsGridView(action, true);
            }
        } else {
            getActionsGridView().setSelectedPosition(actionPosition,
                    new ViewHolderTask() {
                        @Override
                        public void run(RecyclerView.ViewHolder vh) {
                            GuidedActionsStylist.ViewHolder avh =
                                    (GuidedActionsStylist.ViewHolder)vh;
                            if (avh.getAction().hasEditableActivatorView()) {
                                setEditingMode(avh, true /*editing*/, true /*withTransition*/);
                            } else {
                                startExpanded(avh, true);
                            }
                        }
                    });
        }

    }

    /**
     * Collapse expanded action. Do nothing if it is in animation or there is no action expanded.
     *
     * @param withTransition True to run transition animation, false otherwsie.
     */
    public void collapseAction(boolean withTransition) {
        if (isInExpandTransition() || mExpandedAction == null) {
            return;
        }
        boolean runTransition = isExpandTransitionSupported() && withTransition;
        int actionPosition =
                ((GuidedActionAdapter) getActionsGridView().getAdapter()).indexOf(mExpandedAction);
        if (actionPosition < 0) {
            return;
        }
        if (mExpandedAction.hasEditableActivatorView()) {
            setEditingMode(
                    ((ViewHolder) getActionsGridView().findViewHolderForPosition(actionPosition)),
                    false /*editing*/,
                    runTransition);
        } else {
            startExpanded(null, runTransition);
        }
    }

    int getKeyLine() {
        return (int) (mKeyLinePercent * mActionsGridView.getHeight() / 100);
    }

    /**
     * Internal method with assumption we already scroll to the new ViewHolder or is currently
     * expanded.
     */
    void startExpanded(ViewHolder avh, final boolean withTransition) {
        ViewHolder focusAvh = null; // expand / collapse view holder
        final int count = mActionsGridView.getChildCount();
        for (int i = 0; i < count; i++) {
            ViewHolder vh = (ViewHolder) mActionsGridView
                    .getChildViewHolder(mActionsGridView.getChildAt(i));
            if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) {
                // going to collapse this one.
                focusAvh = vh;
                break;
            } else if (avh != null && vh.getAction() == avh.getAction()) {
                // going to expand this one.
                focusAvh = vh;
                break;
            }
        }
        if (focusAvh == null) {
            // huh?
            return;
        }
        boolean isExpand = avh != null;
        boolean isSubActionTransition = focusAvh.getAction().hasSubActions();
        if (withTransition) {
            Object set = TransitionHelper.createTransitionSet(false);
            float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight()
                    : focusAvh.itemView.getHeight() * 0.5f;
            Object slideAndFade = TransitionHelper.createFadeAndShortSlide(
                    Gravity.TOP | Gravity.BOTTOM,
                    slideDistance);
            TransitionHelper.setEpicenterCallback(slideAndFade, new TransitionEpicenterCallback() {
                Rect mRect = new Rect();
                @Override
                public Rect onGetEpicenter(Object transition) {
                    int centerY = getKeyLine();
                    int centerX = 0;
                    mRect.set(centerX, centerY, centerX, centerY);
                    return mRect;
                }
            });
            Object changeFocusItemTransform = TransitionHelper.createChangeTransform();
            Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false);
            Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN
                    | TransitionHelper.FADE_OUT);
            Object changeGridBounds = TransitionHelper.createChangeBounds(false);
            if (avh == null) {
                TransitionHelper.setStartDelay(slideAndFade, 150);
                TransitionHelper.setStartDelay(changeFocusItemTransform, 100);
                TransitionHelper.setStartDelay(changeFocusItemBounds, 100);
                TransitionHelper.setStartDelay(changeGridBounds, 100);
            } else {
                TransitionHelper.setStartDelay(fade, 100);
                TransitionHelper.setStartDelay(changeGridBounds, 50);
                TransitionHelper.setStartDelay(changeFocusItemTransform, 50);
                TransitionHelper.setStartDelay(changeFocusItemBounds, 50);
            }
            for (int i = 0; i < count; i++) {
                ViewHolder vh = (ViewHolder) mActionsGridView
                        .getChildViewHolder(mActionsGridView.getChildAt(i));
                if (vh == focusAvh) {
                    // going to expand/collapse this one.
                    if (isSubActionTransition) {
                        TransitionHelper.include(changeFocusItemTransform, vh.itemView);
                        TransitionHelper.include(changeFocusItemBounds, vh.itemView);
                    }
                } else {
                    // going to slide this item to top / bottom.
                    TransitionHelper.include(slideAndFade, vh.itemView);
                    TransitionHelper.exclude(fade, vh.itemView, true);
                }
            }
            TransitionHelper.include(changeGridBounds, mSubActionsGridView);
            TransitionHelper.include(changeGridBounds, mSubActionsBackground);
            TransitionHelper.addTransition(set, slideAndFade);
            // note that we don't run ChangeBounds for activating view due to the rounding problem
            // of multiple level views ChangeBounds animation causing vertical jittering.
            if (isSubActionTransition) {
                TransitionHelper.addTransition(set, changeFocusItemTransform);
                TransitionHelper.addTransition(set, changeFocusItemBounds);
            }
            TransitionHelper.addTransition(set, fade);
            TransitionHelper.addTransition(set, changeGridBounds);
            mExpandTransition = set;
            TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() {
                @Override
                public void onTransitionEnd(Object transition) {
                    mExpandTransition = null;
                }
            });
            if (isExpand && isSubActionTransition) {
                // To expand sub actions, move original position of sub actions to bottom of item
                int startY = avh.itemView.getBottom();
                mSubActionsGridView.offsetTopAndBottom(startY - mSubActionsGridView.getTop());
                mSubActionsBackground.offsetTopAndBottom(startY - mSubActionsBackground.getTop());
            }
            TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
        }
        onUpdateExpandedViewHolder(avh);
        if (isSubActionTransition) {
            onUpdateSubActionsGridView(focusAvh.getAction(), isExpand);
        }
    }

    /**
     * @return True if sub actions list is expanded.
     */
    public boolean isSubActionsExpanded() {
        return mExpandedAction != null && mExpandedAction.hasSubActions();
    }

    /**
     * @return True if there is {@link #getExpandedAction()} is not null, false otherwise.
     */
    public boolean isExpanded() {
        return mExpandedAction != null;
    }

    /**
     * @return Current expanded GuidedAction or null if not expanded.
     */
    @Nullable
    public GuidedAction getExpandedAction() {
        return mExpandedAction;
    }

    /**
     * Expand or collapse GuidedActionStylist.
     * @param avh When not null, the GuidedActionStylist expands the sub actions of avh.  When null
     * the GuidedActionStylist will collapse sub actions.
     */
    public void onUpdateExpandedViewHolder(@Nullable ViewHolder avh) {

        // Note about setting the prune child flag back & forth here: without this, the actions that
        // go off the screen from the top or bottom become invisible forever. This is because once
        // an action is expanded, it takes more space which in turn kicks out some other actions
        // off of the screen. Once, this action is collapsed (after the second click) and the
        // visibility flag is set back to true for all existing actions,
        // the off-the-screen actions are pruned from the view, thus
        // could not be accessed, had we not disabled pruning prior to this.
        if (avh == null) {
            mExpandedAction = null;
            mActionsGridView.setPruneChild(true);
        } else if (avh.getAction() != mExpandedAction) {
            mExpandedAction = avh.getAction();
            mActionsGridView.setPruneChild(false);
        }
        // In expanding mode, notifyItemChange on expanded item will reset the translationY by
        // the default ItemAnimator.  So disable ItemAnimation in expanding mode.
        mActionsGridView.setAnimateChildLayout(false);
        final int count = mActionsGridView.getChildCount();
        for (int i = 0; i < count; i++) {
            ViewHolder vh = (ViewHolder) mActionsGridView
                    .getChildViewHolder(mActionsGridView.getChildAt(i));
            updateChevronAndVisibility(vh);
        }
    }

    void onUpdateSubActionsGridView(GuidedAction action, boolean expand) {
        if (mSubActionsGridView != null) {
            ViewGroup.MarginLayoutParams lp =
                    (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
            GuidedActionAdapter adapter = (GuidedActionAdapter) mSubActionsGridView.getAdapter();
            if (expand) {
                // set to negative value so GuidedActionRelativeLayout will override with
                // keyLine percentage.
                lp.topMargin = -2;
                lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
                mSubActionsGridView.setLayoutParams(lp);
                mSubActionsGridView.setVisibility(View.VISIBLE);
                mSubActionsBackground.setVisibility(View.VISIBLE);
                mSubActionsGridView.requestFocus();
                adapter.setActions(action.getSubActions());
            } else {
                // set to explicit value, which will disable the keyLine percentage calculation
                // in GuidedRelativeLayout.
                int actionPosition = ((GuidedActionAdapter) mActionsGridView.getAdapter())
                        .indexOf(action);
                lp.topMargin = mActionsGridView.getLayoutManager()
                        .findViewByPosition(actionPosition).getBottom();
                lp.height = 0;
                mSubActionsGridView.setVisibility(View.INVISIBLE);
                mSubActionsBackground.setVisibility(View.INVISIBLE);
                mSubActionsGridView.setLayoutParams(lp);
                adapter.setActions(Collections.<GuidedAction>emptyList());
                mActionsGridView.requestFocus();
            }
        }
    }

    private void updateChevronAndVisibility(ViewHolder vh) {
        if (!vh.isSubAction()) {
            if (mExpandedAction == null) {
                vh.itemView.setVisibility(View.VISIBLE);
                vh.itemView.setTranslationY(0);
                if (vh.mActivatorView != null) {
                    vh.setActivated(false);
                }
            } else if (vh.getAction() == mExpandedAction) {
                vh.itemView.setVisibility(View.VISIBLE);
                if (vh.getAction().hasSubActions()) {
                    vh.itemView.setTranslationY(getKeyLine() - vh.itemView.getBottom());
                } else if (vh.mActivatorView != null) {
                    vh.itemView.setTranslationY(0);
                    vh.setActivated(true);
                }
            } else {
                vh.itemView.setVisibility(View.INVISIBLE);
                vh.itemView.setTranslationY(0);
            }
        }
        if (vh.mChevronView != null) {
            onBindChevronView(vh, vh.getAction());
        }
    }

    /*
     * ==========================================
     * FragmentAnimationProvider overrides
     * ==========================================
     */

    /**
     * {@inheritDoc}
     */
    @Override
    public void onImeAppearing(@NonNull List<Animator> animators) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onImeDisappearing(@NonNull List<Animator> animators) {
    }

    /*
     * ==========================================
     * Private methods
     * ==========================================
     */

    private static float getFloat(Context ctx, TypedValue typedValue, int attrId) {
        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
        return typedValue.getFloat();
    }

    private static float getFloatValue(Resources resources, TypedValue typedValue, int resId) {
        resources.getValue(resId, typedValue, true);
        return typedValue.getFloat();
    }

    private static int getInteger(Context ctx, TypedValue typedValue, int attrId) {
        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
        return ctx.getResources().getInteger(typedValue.resourceId);
    }

    private static int getDimension(Context ctx, TypedValue typedValue, int attrId) {
        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
        return ctx.getResources().getDimensionPixelSize(typedValue.resourceId);
    }

    private boolean setIcon(final ImageView iconView, GuidedAction action) {
        Drawable icon = null;
        if (iconView != null) {
            icon = action.getIcon();
            if (icon != null) {
                // setImageDrawable resets the drawable's level unless we set the view level first.
                iconView.setImageLevel(icon.getLevel());
                iconView.setImageDrawable(icon);
                iconView.setVisibility(View.VISIBLE);
            } else {
                iconView.setVisibility(View.GONE);
            }
        }
        return icon != null;
    }

    /**
     * @return the max height in pixels the description can be such that the
     *         action nicely takes up the entire screen.
     */
    private int getDescriptionMaxHeight(TextView title) {
        // The 2 multiplier on the title height calculation is a
        // conservative estimate for font padding which can not be
        // calculated at this stage since the view hasn't been rendered yet.
        return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight());
    }

    @RequiresApi(26)
    static class Api26Impl {
        private Api26Impl() {
            // This class is not instantiable.
        }

        @androidx.annotation.DoNotInline
        static void setAutofillHints(View view, String... autofillHints) {
            view.setAutofillHints(autofillHints);
        }

        @androidx.annotation.DoNotInline
        static void setImportantForAutofill(
                View view,
                @SuppressWarnings("SameParameterValue") int mode
        ) {
            view.setImportantForAutofill(mode);
        }
    }
}