1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package androidx.leanback.widget;
15 
16 import android.annotation.SuppressLint;
17 import android.content.Context;
18 import android.content.Intent;
19 import android.graphics.drawable.Drawable;
20 import android.os.Bundle;
21 import android.text.InputType;
22 
23 import androidx.annotation.DrawableRes;
24 import androidx.annotation.StringRes;
25 import androidx.core.content.ContextCompat;
26 import androidx.leanback.R;
27 
28 import org.jspecify.annotations.NonNull;
29 import org.jspecify.annotations.Nullable;
30 
31 import java.util.List;
32 
33 /**
34  * A data class which represents an action within a {@link
35  * androidx.leanback.app.GuidedStepFragment}. GuidedActions contain at minimum a title
36  * and a description, and typically also an icon.
37  * <p>
38  * A GuidedAction typically represents a single action a user may take, but may also represent a
39  * possible choice out of a group of mutually exclusive choices (similar to radio buttons), or an
40  * information-only label (in which case the item cannot be clicked).
41  * <p>
42  * GuidedActions may optionally be checked. They may also indicate that they will request further
43  * user input on selection, in which case they will be displayed with a chevron indicator.
44  * <p>
45  * GuidedAction recommends to use {@link Builder}. When application subclass GuidedAction, it
46  * can subclass {@link BuilderBase}, implement its own builder() method where it should
47  * call {@link BuilderBase#applyValues(GuidedAction)}.
48  */
49 public class GuidedAction extends Action {
50 
51     private static final String TAG = "GuidedAction";
52 
53     /**
54      * Special check set Id that is neither checkbox nor radio.
55      */
56     public static final int NO_CHECK_SET = 0;
57     /**
58      * Default checkset Id for radio.
59      */
60     public static final int DEFAULT_CHECK_SET_ID = 1;
61     /**
62      * Checkset Id for checkbox.
63      */
64     public static final int CHECKBOX_CHECK_SET_ID = -1;
65 
66     /**
67      * When finishing editing, goes to next action.
68      */
69     public static final long ACTION_ID_NEXT = -2;
70     /**
71      * When finishing editing, stay on current action.
72      */
73     public static final long ACTION_ID_CURRENT = -3;
74 
75     /**
76      * Id of standard OK action.
77      */
78     public static final long ACTION_ID_OK = -4;
79 
80     /**
81      * Id of standard Cancel action.
82      */
83     public static final long ACTION_ID_CANCEL = -5;
84 
85     /**
86      * Id of standard Finish action.
87      */
88     public static final long ACTION_ID_FINISH = -6;
89 
90     /**
91      * Id of standard Finish action.
92      */
93     public static final long ACTION_ID_CONTINUE = -7;
94 
95     /**
96      * Id of standard Yes action.
97      */
98     public static final long ACTION_ID_YES = -8;
99 
100     /**
101      * Id of standard No action.
102      */
103     public static final long ACTION_ID_NO = -9;
104 
105     static final int EDITING_NONE = 0;
106     static final int EDITING_TITLE = 1;
107     static final int EDITING_DESCRIPTION = 2;
108     static final int EDITING_ACTIVATOR_VIEW = 3;
109 
110     /**
111      * Base builder class to build a {@link GuidedAction} object.  When subclass GuidedAction, you
112      * can override this BuilderBase class, implements your build() method which should call
113      * {@link #applyValues(GuidedAction)}.  When using GuidedAction directly, use {@link Builder}.
114      *
115      * @param <B> the type of BuilderBase
116      */
117     @SuppressWarnings("unchecked")
118     public abstract static class BuilderBase<B extends BuilderBase> {
119         private Context mContext;
120         private long mId;
121         private CharSequence mTitle;
122         private CharSequence mEditTitle;
123         private CharSequence mDescription;
124         private CharSequence mEditDescription;
125         private String[] mAutofillHints;
126         private Drawable mIcon;
127         /**
128          * The mActionFlags holds various action states such as whether title or description are
129          * editable, or the action is focusable.
130          *
131          */
132         private int mActionFlags;
133 
134         private int mEditable = EDITING_NONE;
135         private int mInputType = InputType.TYPE_CLASS_TEXT
136                 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
137         private int mDescriptionInputType = InputType.TYPE_CLASS_TEXT
138                 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
139         private int mEditInputType = InputType.TYPE_CLASS_TEXT;
140         private int mDescriptionEditInputType = InputType.TYPE_CLASS_TEXT;
141         private int mCheckSetId = NO_CHECK_SET;
142         private List<GuidedAction> mSubActions;
143         private Intent mIntent;
144 
145         /**
146          * Creates a BuilderBase for GuidedAction or its subclass.
147          * @param context Context object used to build the GuidedAction.
148          */
BuilderBase(@onNull Context context)149         public BuilderBase(@NonNull Context context) {
150             mContext = context;
151             mActionFlags = PF_ENABLED | PF_FOCUSABLE | PF_AUTORESTORE;
152         }
153 
154         /**
155          * Returns Context of this Builder.
156          * @return Context of this Builder.
157          */
getContext()158         public @NonNull Context getContext() {
159             return mContext;
160         }
161 
setFlags(int flag, int mask)162         private void setFlags(int flag, int mask) {
163             mActionFlags = (mActionFlags & ~mask) | (flag & mask);
164         }
165 
166         /**
167          * Subclass of BuilderBase should call this function to apply values.
168          * @param action GuidedAction to apply BuilderBase values.
169          */
applyValues(@onNull GuidedAction action)170         protected final void applyValues(@NonNull GuidedAction action) {
171             // Base Action values
172             action.setId(mId);
173             action.setLabel1(mTitle);
174             action.setEditTitle(mEditTitle);
175             action.setLabel2(mDescription);
176             action.setEditDescription(mEditDescription);
177             action.setIcon(mIcon);
178 
179             // Subclass values
180             action.mIntent = mIntent;
181             action.mEditable = mEditable;
182             action.mInputType = mInputType;
183             action.mDescriptionInputType = mDescriptionInputType;
184             action.mAutofillHints = mAutofillHints;
185             action.mEditInputType = mEditInputType;
186             action.mDescriptionEditInputType = mDescriptionEditInputType;
187             action.mActionFlags = mActionFlags;
188             action.mCheckSetId = mCheckSetId;
189             action.mSubActions = mSubActions;
190         }
191 
192         /**
193          * Construct a clickable action with associated id and auto assign pre-defined title for the
194          * action. If the id is not supported, the method simply does nothing.
195          * @param id One of {@link GuidedAction#ACTION_ID_OK} {@link GuidedAction#ACTION_ID_CANCEL}
196          * {@link GuidedAction#ACTION_ID_FINISH} {@link GuidedAction#ACTION_ID_CONTINUE}
197          * {@link GuidedAction#ACTION_ID_YES} {@link GuidedAction#ACTION_ID_NO}.
198          * @return The same BuilderBase object.
199          */
clickAction(long id)200         public B clickAction(long id) {
201             if (id == ACTION_ID_OK) {
202                 mId = ACTION_ID_OK;
203                 mTitle = mContext.getString(android.R.string.ok);
204             } else if (id == ACTION_ID_CANCEL) {
205                 mId = ACTION_ID_CANCEL;
206                 mTitle = mContext.getString(android.R.string.cancel);
207             } else if (id == ACTION_ID_FINISH) {
208                 mId = ACTION_ID_FINISH;
209                 mTitle = mContext.getString(R.string.lb_guidedaction_finish_title);
210             } else if (id == ACTION_ID_CONTINUE) {
211                 mId = ACTION_ID_CONTINUE;
212                 mTitle = mContext.getString(R.string.lb_guidedaction_continue_title);
213             } else if (id == ACTION_ID_YES) {
214                 mId = ACTION_ID_YES;
215                 mTitle = mContext.getString(android.R.string.ok);
216             } else if (id == ACTION_ID_NO) {
217                 mId = ACTION_ID_NO;
218                 mTitle = mContext.getString(android.R.string.cancel);
219             }
220             return (B) this;
221         }
222 
223         /**
224          * Sets the ID associated with this action.  The ID can be any value the client wishes;
225          * it is typically used to determine what to do when an action is clicked.
226          * @param id The ID to associate with this action.
227          */
id(long id)228         public B id(long id) {
229             mId = id;
230             return (B) this;
231         }
232 
233         /**
234          * Sets the title for this action.  The title is typically a short string indicating the
235          * action to be taken on click, e.g. "Continue" or "Cancel".
236          * @param title The title for this action.
237          */
title(@ullable CharSequence title)238         public B title(@Nullable CharSequence title) {
239             mTitle = title;
240             return (B) this;
241         }
242 
243         /**
244          * Sets the title for this action.  The title is typically a short string indicating the
245          * action to be taken on click, e.g. "Continue" or "Cancel".
246          * @param titleResourceId The resource id of title for this action.
247          */
title(@tringRes int titleResourceId)248         public B title(@StringRes int titleResourceId) {
249             mTitle = getContext().getString(titleResourceId);
250             return (B) this;
251         }
252 
253         /**
254          * Sets the optional title text to edit.  When TextView is activated, the edit title
255          * replaces the string of title.
256          * @param editTitle The optional title text to edit when TextView is activated.
257          */
editTitle(@ullable CharSequence editTitle)258         public B editTitle(@Nullable CharSequence editTitle) {
259             mEditTitle = editTitle;
260             return (B) this;
261         }
262 
263         /**
264          * Sets the optional title text to edit.  When TextView is activated, the edit title
265          * replaces the string of title.
266          * @param editTitleResourceId String resource id of the optional title text to edit when
267          * TextView is activated.
268          */
editTitle(@tringRes int editTitleResourceId)269         public B editTitle(@StringRes int editTitleResourceId) {
270             mEditTitle = getContext().getString(editTitleResourceId);
271             return (B) this;
272         }
273 
274         /**
275          * Sets the description for this action.  The description is typically a longer string
276          * providing extra information on what the action will do.
277          * @param description The description for this action.
278          */
description(@ullable CharSequence description)279         public B description(@Nullable CharSequence description) {
280             mDescription = description;
281             return (B) this;
282         }
283 
284         /**
285          * Sets the description for this action.  The description is typically a longer string
286          * providing extra information on what the action will do.
287          * @param descriptionResourceId String resource id of the description for this action.
288          */
description(@tringRes int descriptionResourceId)289         public B description(@StringRes int descriptionResourceId) {
290             mDescription = getContext().getString(descriptionResourceId);
291             return (B) this;
292         }
293 
294         /**
295          * Sets the optional description text to edit.  When TextView is activated, the edit
296          * description replaces the string of description.
297          * @param description The description to edit for this action.
298          */
editDescription(@ullable CharSequence description)299         public B editDescription(@Nullable CharSequence description) {
300             mEditDescription = description;
301             return (B) this;
302         }
303 
304         /**
305          * Sets the optional description text to edit.  When TextView is activated, the edit
306          * description replaces the string of description.
307          * @param descriptionResourceId String resource id of the description to edit for this
308          * action.
309          */
editDescription(@tringRes int descriptionResourceId)310         public B editDescription(@StringRes int descriptionResourceId) {
311             mEditDescription = getContext().getString(descriptionResourceId);
312             return (B) this;
313         }
314 
315         /**
316          * Sets the intent associated with this action.  Clients would typically fire this intent
317          * directly when the action is clicked.
318          * @param intent The intent associated with this action.
319          */
intent(@ullable Intent intent)320         public B intent(@Nullable Intent intent) {
321             mIntent = intent;
322             return (B) this;
323         }
324 
325         /**
326          * Sets the action's icon drawable.
327          * @param icon The drawable for the icon associated with this action.
328          */
icon(@ullable Drawable icon)329         public B icon(@Nullable Drawable icon) {
330             mIcon = icon;
331             return (B) this;
332         }
333 
334         /**
335          * Sets the action's icon drawable by retrieving it by resource ID from the specified
336          * context. This is a convenience function that simply looks up the drawable and calls
337          * {@link #icon(Drawable)}.
338          * @param iconResourceId The resource ID for the icon associated with this action.
339          * @param context The context whose resource ID should be retrieved.
340          * @deprecated Use {@link #icon(int)}.
341          */
342         @Deprecated
iconResourceId(@rawableRes int iconResourceId, Context context)343         public B iconResourceId(@DrawableRes int iconResourceId, Context context) {
344             return icon(ContextCompat.getDrawable(context, iconResourceId));
345         }
346 
347         /**
348          * Sets the action's icon drawable by retrieving it by resource ID from Builder's
349          * context. This is a convenience function that simply looks up the drawable and calls
350          * {@link #icon(Drawable)}.
351          * @param iconResourceId The resource ID for the icon associated with this action.
352          */
icon(@rawableRes int iconResourceId)353         public B icon(@DrawableRes int iconResourceId) {
354             return icon(ContextCompat.getDrawable(getContext(), iconResourceId));
355         }
356 
357         /**
358          * Indicates whether this action title is editable. Note: Editable actions cannot also be
359          * checked, or belong to a check set.
360          * @param editable Whether this action is editable.
361          */
editable(boolean editable)362         public B editable(boolean editable) {
363             if (!editable) {
364                 if (mEditable == EDITING_TITLE) {
365                     mEditable = EDITING_NONE;
366                 }
367                 return (B) this;
368             }
369             mEditable = EDITING_TITLE;
370             if (isChecked() || mCheckSetId != NO_CHECK_SET) {
371                 throw new IllegalArgumentException("Editable actions cannot also be checked");
372             }
373             return (B) this;
374         }
375 
376         /**
377          * Indicates whether this action's description is editable
378          * @param editable Whether this action description is editable.
379          */
descriptionEditable(boolean editable)380         public B descriptionEditable(boolean editable) {
381             if (!editable) {
382                 if (mEditable == EDITING_DESCRIPTION) {
383                     mEditable = EDITING_NONE;
384                 }
385                 return (B) this;
386             }
387             mEditable = EDITING_DESCRIPTION;
388             if (isChecked() || mCheckSetId != NO_CHECK_SET) {
389                 throw new IllegalArgumentException("Editable actions cannot also be checked");
390             }
391             return (B) this;
392         }
393 
394         /**
395          * Indicates whether this action has a view can be activated to edit, e.g. a DatePicker.
396          * @param editable Whether this action has view can be activated to edit.
397          */
hasEditableActivatorView(boolean editable)398         public B hasEditableActivatorView(boolean editable) {
399             if (!editable) {
400                 if (mEditable == EDITING_ACTIVATOR_VIEW) {
401                     mEditable = EDITING_NONE;
402                 }
403                 return (B) this;
404             }
405             mEditable = EDITING_ACTIVATOR_VIEW;
406             if (isChecked() || mCheckSetId != NO_CHECK_SET) {
407                 throw new IllegalArgumentException("Editable actions cannot also be checked");
408             }
409             return (B) this;
410         }
411 
412         /**
413          * Sets {@link InputType} of this action title not in editing.
414          *
415          * @param inputType InputType for the action title not in editing.
416          */
inputType(int inputType)417         public B inputType(int inputType) {
418             mInputType = inputType;
419             return (B) this;
420         }
421 
422         /**
423          * Sets {@link InputType} of this action description not in editing.
424          *
425          * @param inputType InputType for the action description not in editing.
426          */
descriptionInputType(int inputType)427         public B descriptionInputType(int inputType) {
428             mDescriptionInputType = inputType;
429             return (B) this;
430         }
431 
432 
433         /**
434          * Sets {@link InputType} of this action title in editing.
435          *
436          * @param inputType InputType for the action title in editing.
437          */
editInputType(int inputType)438         public B editInputType(int inputType) {
439             mEditInputType = inputType;
440             return (B) this;
441         }
442 
443         /**
444          * Sets {@link InputType} of this action description in editing.
445          *
446          * @param inputType InputType for the action description in editing.
447          */
descriptionEditInputType(int inputType)448         public B descriptionEditInputType(int inputType) {
449             mDescriptionEditInputType = inputType;
450             return (B) this;
451         }
452 
453 
isChecked()454         private boolean isChecked() {
455             return (mActionFlags & PF_CHECKED) == PF_CHECKED;
456         }
457         /**
458          * Indicates whether this action is initially checked.
459          * @param checked Whether this action is checked.
460          */
checked(boolean checked)461         public B checked(boolean checked) {
462             setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
463             if (mEditable != EDITING_NONE) {
464                 throw new IllegalArgumentException("Editable actions cannot also be checked");
465             }
466             return (B) this;
467         }
468 
469         /**
470          * Indicates whether this action is part of a single-select group similar to radio buttons
471          * or this action is a checkbox. When one item in a check set is checked, all others with
472          * the same check set ID will be checked automatically.
473          * @param checkSetId The check set ID, or {@link GuidedAction#NO_CHECK_SET} to indicate not
474          * radio or checkbox, or {@link GuidedAction#CHECKBOX_CHECK_SET_ID} to indicate a checkbox.
475          */
checkSetId(int checkSetId)476         public B checkSetId(int checkSetId) {
477             mCheckSetId = checkSetId;
478             if (mEditable != EDITING_NONE) {
479                 throw new IllegalArgumentException("Editable actions cannot also be in check sets");
480             }
481             return (B) this;
482         }
483 
484         /**
485          * Indicates whether the title and description are long, and should be displayed
486          * appropriately.
487          * @param multilineDescription Whether this action has a multiline description.
488          */
multilineDescription(boolean multilineDescription)489         public B multilineDescription(boolean multilineDescription) {
490             setFlags(multilineDescription ? PF_MULTI_LINE_DESCRIPTION : 0,
491                 PF_MULTI_LINE_DESCRIPTION);
492             return (B) this;
493         }
494 
495         /**
496          * Indicates whether this action has a next state and should display a chevron.
497          * @param hasNext Whether this action has a next state.
498          */
hasNext(boolean hasNext)499         public B hasNext(boolean hasNext) {
500             setFlags(hasNext ? PF_HAS_NEXT : 0, PF_HAS_NEXT);
501             return (B) this;
502         }
503 
504         /**
505          * Indicates whether this action is for information purposes only and cannot be clicked.
506          * @param infoOnly Whether this action has a next state.
507          */
infoOnly(boolean infoOnly)508         public B infoOnly(boolean infoOnly) {
509             setFlags(infoOnly ? PF_INFO_ONLY : 0, PF_INFO_ONLY);
510             return (B) this;
511         }
512 
513         /**
514          * Indicates whether this action is enabled.  If not enabled, an action cannot be clicked.
515          * @param enabled Whether the action is enabled.
516          */
enabled(boolean enabled)517         public B enabled(boolean enabled) {
518             setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
519             return (B) this;
520         }
521 
522         /**
523          * Indicates whether this action can take focus.
524          * @param focusable
525          * @return The same BuilderBase object.
526          */
focusable(boolean focusable)527         public B focusable(boolean focusable) {
528             setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
529             return (B) this;
530         }
531 
532         /**
533          * Sets sub actions list.
534          * @param subActions
535          * @return The same BuilderBase object.
536          */
subActions(@ullable List<GuidedAction> subActions)537         public B subActions(@Nullable List<GuidedAction> subActions) {
538             mSubActions = subActions;
539             return (B) this;
540         }
541 
542         /**
543          * Explicitly sets auto restore feature on the GuidedAction.  It's by default true.
544          * @param autoSaveRestoreEnabled True if turn on auto save/restore of GuidedAction content,
545          *                                false otherwise.
546          * @return The same BuilderBase object.
547          * @see GuidedAction#isAutoSaveRestoreEnabled()
548          */
autoSaveRestoreEnabled(boolean autoSaveRestoreEnabled)549         public B autoSaveRestoreEnabled(boolean autoSaveRestoreEnabled) {
550             setFlags(autoSaveRestoreEnabled ? PF_AUTORESTORE : 0, PF_AUTORESTORE);
551             return (B) this;
552         }
553 
554         /**
555          * Sets autofill hints. See {@link android.view.View#setAutofillHints}
556          * @param hints List of hints for autofill.
557          * @return The same BuilderBase object.
558          */
autofillHints(String @ullable .... hints)559         public B autofillHints(String @Nullable ... hints) {
560             mAutofillHints = hints;
561             return (B) this;
562         }
563     }
564 
565     /**
566      * Builds a {@link GuidedAction} object.
567      */
568     public static class Builder extends BuilderBase<Builder> {
569 
570         /**
571          * @deprecated Use {@link GuidedAction.Builder#Builder(Context)}.
572          */
573         @Deprecated
Builder()574         public Builder() {
575             super(null);
576         }
577 
578         /**
579          * Creates a Builder for GuidedAction.
580          * @param context Context to build GuidedAction.
581          */
Builder(@ullable Context context)582         public Builder(@Nullable Context context) {
583             super(context);
584         }
585 
586         /**
587          * Builds the GuidedAction corresponding to this Builder.
588          * @return The GuidedAction as configured through this Builder.
589          */
build()590         public @NonNull GuidedAction build() {
591             GuidedAction action = new GuidedAction();
592             applyValues(action);
593             return action;
594         }
595 
596     }
597 
598     static final int PF_CHECKED = 0x00000001;
599     static final int PF_MULTI_LINE_DESCRIPTION = 0x00000002;
600     static final int PF_HAS_NEXT = 0x00000004;
601     static final int PF_INFO_ONLY = 0x00000008;
602     static final int PF_ENABLED = 0x00000010;
603     static final int PF_FOCUSABLE = 0x00000020;
604     static final int PF_AUTORESTORE = 0x00000040;
605     int mActionFlags;
606 
607     private CharSequence mEditTitle;
608     private CharSequence mEditDescription;
609     int mEditable;
610     int mInputType;
611     int mDescriptionInputType;
612     int mEditInputType;
613     int mDescriptionEditInputType;
614     String[] mAutofillHints;
615 
616     int mCheckSetId;
617 
618     List<GuidedAction> mSubActions;
619 
620     Intent mIntent;
621 
GuidedAction()622     protected GuidedAction() {
623         super(0);
624     }
625 
setFlags(int flag, int mask)626     private void setFlags(int flag, int mask) {
627         mActionFlags = (mActionFlags & ~mask) | (flag & mask);
628     }
629 
630     /**
631      * Returns the title of this action.
632      * @return The title set when this action was built.
633      */
getTitle()634     public @Nullable CharSequence getTitle() {
635         return getLabel1();
636     }
637 
638     /**
639      * Sets the title of this action.
640      * @param title The title set when this action was built.
641      */
setTitle(@ullable CharSequence title)642     public void setTitle(@Nullable CharSequence title) {
643         setLabel1(title);
644     }
645 
646     /**
647      * Returns the optional title text to edit.  When not null, it is being edited instead of
648      * {@link #getTitle()}.
649      * @return Optional title text to edit instead of {@link #getTitle()}.
650      */
getEditTitle()651     public @Nullable CharSequence getEditTitle() {
652         return mEditTitle;
653     }
654 
655     /**
656      * Sets the optional title text to edit instead of {@link #setTitle(CharSequence)}.
657      * @param editTitle Optional title text to edit instead of {@link #setTitle(CharSequence)}.
658      */
setEditTitle(@ullable CharSequence editTitle)659     public void setEditTitle(@Nullable CharSequence editTitle) {
660         mEditTitle = editTitle;
661     }
662 
663     /**
664      * Returns the optional description text to edit.  When not null, it is being edited instead of
665      * {@link #getDescription()}.
666      * @return Optional description text to edit instead of {@link #getDescription()}.
667      */
getEditDescription()668     public @Nullable CharSequence getEditDescription() {
669         return mEditDescription;
670     }
671 
672     /**
673      * Sets the optional description text to edit instead of {@link #setDescription(CharSequence)}.
674      * @param editDescription Optional description text to edit instead of
675      * {@link #setDescription(CharSequence)}.
676      */
setEditDescription(@ullable CharSequence editDescription)677     public void setEditDescription(@Nullable CharSequence editDescription) {
678         mEditDescription = editDescription;
679     }
680 
681     /**
682      * Returns true if {@link #getEditTitle()} is not null.  When true, the {@link #getEditTitle()}
683      * is being edited instead of {@link #getTitle()}.
684      * @return true if {@link #getEditTitle()} is not null.
685      */
isEditTitleUsed()686     public boolean isEditTitleUsed() {
687         return mEditTitle != null;
688     }
689 
690     /**
691      * Returns the description of this action.
692      * @return The description of this action.
693      */
getDescription()694     public @Nullable CharSequence getDescription() {
695         return getLabel2();
696     }
697 
698     /**
699      * Sets the description of this action.
700      * @param description The description of the action.
701      */
setDescription(@ullable CharSequence description)702     public void setDescription(@Nullable CharSequence description) {
703         setLabel2(description);
704     }
705 
706     /**
707      * Returns the intent associated with this action.
708      * @return The intent set when this action was built.
709      */
getIntent()710     public @Nullable Intent getIntent() {
711         return mIntent;
712     }
713 
714     /**
715      * Sets the intent of this action.
716      * @param intent New intent to set on this action.
717      */
setIntent(@ullable Intent intent)718     public void setIntent(@Nullable Intent intent) {
719         mIntent = intent;
720     }
721 
722     /**
723      * Returns whether this action title is editable.
724      * @return true if the action title is editable, false otherwise.
725      */
isEditable()726     public boolean isEditable() {
727         return mEditable == EDITING_TITLE;
728     }
729 
730     /**
731      * Returns whether this action description is editable.
732      * @return true if the action description is editable, false otherwise.
733      */
isDescriptionEditable()734     public boolean isDescriptionEditable() {
735         return mEditable == EDITING_DESCRIPTION;
736     }
737 
738     /**
739      * Returns if this action has editable title or editable description.
740      * @return True if this action has editable title or editable description, false otherwise.
741      */
hasTextEditable()742     public boolean hasTextEditable() {
743         return mEditable == EDITING_TITLE || mEditable == EDITING_DESCRIPTION;
744     }
745 
746     /**
747      * Returns whether this action can be activated to edit, e.g. a DatePicker.
748      * @return true if the action can be activated to edit.
749      */
hasEditableActivatorView()750     public boolean hasEditableActivatorView() {
751         return mEditable == EDITING_ACTIVATOR_VIEW;
752     }
753 
754     /**
755      * Returns InputType of action title in editing; only valid when {@link #isEditable()} is true.
756      * @return InputType of action title in editing.
757      */
getEditInputType()758     public int getEditInputType() {
759         return mEditInputType;
760     }
761 
762     /**
763      * Returns InputType of action description in editing; only valid when
764      * {@link #isDescriptionEditable()} is true.
765      * @return InputType of action description in editing.
766      */
getDescriptionEditInputType()767     public int getDescriptionEditInputType() {
768         return mDescriptionEditInputType;
769     }
770 
771     /**
772      * Returns InputType of action title not in editing.
773      * @return InputType of action title not in editing.
774      */
getInputType()775     public int getInputType() {
776         return mInputType;
777     }
778 
779     /**
780      * Returns InputType of action description not in editing.
781      * @return InputType of action description not in editing.
782      */
getDescriptionInputType()783     public int getDescriptionInputType() {
784         return mDescriptionInputType;
785     }
786 
787     /**
788      * Returns whether this action is checked.
789      * @return true if the action is currently checked, false otherwise.
790      */
isChecked()791     public boolean isChecked() {
792         return (mActionFlags & PF_CHECKED) == PF_CHECKED;
793     }
794 
795     /**
796      * Sets whether this action is checked.
797      * @param checked Whether this action should be checked.
798      */
setChecked(boolean checked)799     public void setChecked(boolean checked) {
800         setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
801     }
802 
803     /**
804      * Returns the check set id this action is a part of. All actions in the same list with the same
805      * check set id are considered linked. When one of the actions within that set is selected, that
806      * action becomes checked, while all the other actions become unchecked.
807      *
808      * @return an integer representing the check set this action is a part of, or
809      *         {@link #CHECKBOX_CHECK_SET_ID} if this is a checkbox, or {@link #NO_CHECK_SET} if
810      *         this action is not a checkbox or radiobutton.
811      */
getCheckSetId()812     public int getCheckSetId() {
813         return mCheckSetId;
814     }
815 
816     /**
817      * Returns whether this action is has a multiline description.
818      * @return true if the action was constructed as having a multiline description, false
819      * otherwise.
820      */
hasMultilineDescription()821     public boolean hasMultilineDescription() {
822         return (mActionFlags & PF_MULTI_LINE_DESCRIPTION) == PF_MULTI_LINE_DESCRIPTION;
823     }
824 
825     /**
826      * Returns whether this action is enabled.
827      * @return true if the action is currently enabled, false otherwise.
828      */
isEnabled()829     public boolean isEnabled() {
830         return (mActionFlags & PF_ENABLED) == PF_ENABLED;
831     }
832 
833     /**
834      * Sets whether this action is enabled.
835      * @param enabled Whether this action should be enabled.
836      */
setEnabled(boolean enabled)837     public void setEnabled(boolean enabled) {
838         setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
839     }
840 
841     /**
842      * Returns whether this action is focusable.
843      * @return true if the action is currently focusable, false otherwise.
844      */
isFocusable()845     public boolean isFocusable() {
846         return (mActionFlags & PF_FOCUSABLE) == PF_FOCUSABLE;
847     }
848 
849     /**
850      * Sets whether this action is focusable.
851      * @param focusable Whether this action should be focusable.
852      */
setFocusable(boolean focusable)853     public void setFocusable(boolean focusable) {
854         setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
855     }
856 
857     /**
858      * Returns autofill hints, see {@link android.view.View#setAutofillHints(String...)}.
859      */
getAutofillHints()860     public String[] getAutofillHints() {
861         return mAutofillHints;
862     }
863 
864     /**
865      * Returns whether this action will request further user input when selected, such as showing
866      * another GuidedStepFragment or launching a new activity. Configured during construction.
867      * @return true if the action will request further user input when selected, false otherwise.
868      */
hasNext()869     public boolean hasNext() {
870         return (mActionFlags & PF_HAS_NEXT) == PF_HAS_NEXT;
871     }
872 
873     /**
874      * Returns whether the action will only display information and is thus not clickable. If both
875      * this and {@link #hasNext()} are true, infoOnly takes precedence. The default is false. For
876      * example, this might represent e.g. the amount of storage a document uses, or the cost of an
877      * app.
878      * @return true if will only display information, false otherwise.
879      */
infoOnly()880     public boolean infoOnly() {
881         return (mActionFlags & PF_INFO_ONLY) == PF_INFO_ONLY;
882     }
883 
884     /**
885      * Change sub actions list.
886      * @param actions Sub actions list to set on this action.  Sets null to disable sub actions.
887      */
setSubActions(@ullable List<GuidedAction> actions)888     public void setSubActions(@Nullable List<GuidedAction> actions) {
889         mSubActions = actions;
890     }
891 
892     /**
893      * @return List of sub actions or null if sub actions list is not enabled.
894      */
895     @SuppressLint("NullableCollection")
getSubActions()896     public @Nullable List<GuidedAction> getSubActions() {
897         return mSubActions;
898     }
899 
900     /**
901      * @return True if has sub actions list, even it's currently empty.
902      */
hasSubActions()903     public boolean hasSubActions() {
904         return mSubActions != null;
905     }
906 
907     /**
908      * Returns true if Action will be saved to instanceState and restored later, false otherwise.
909      * The default value is true.  When isAutoSaveRestoreEnabled() is true and {@link #getId()} is
910      * not {@link #NO_ID}:
911      * <ul>
912      * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
913      * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
914      * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
915      * <li>{@link GuidedDatePickerAction} will be saved</li>
916      * </ul>
917      * App may explicitly disable auto restore and handle by itself. App should override Fragment
918      * onSaveInstanceState() and onCreateActions()
919      * @return True if Action will be saved to instanceState and restored later, false otherwise.
920      */
isAutoSaveRestoreEnabled()921     public final boolean isAutoSaveRestoreEnabled() {
922         return (mActionFlags & PF_AUTORESTORE) == PF_AUTORESTORE;
923     }
924 
925     /**
926      * Save action into a bundle using a given key. When isAutoRestoreEna() is true:
927      * <ul>
928      * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
929      * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
930      * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
931      * <li>{@link GuidedDatePickerAction} will be saved</li>
932      * </ul>
933      * Subclass may override this method.
934      * @param bundle  Bundle to save the Action.
935      * @param key Key used to save the Action.
936      */
onSaveInstanceState(@onNull Bundle bundle, @NonNull String key)937     public void onSaveInstanceState(@NonNull Bundle bundle, @NonNull String key) {
938         if (needAutoSaveTitle() && getTitle() != null) {
939             bundle.putString(key, getTitle().toString());
940         } else if (needAutoSaveDescription() && getDescription() != null) {
941             bundle.putString(key, getDescription().toString());
942         } else if (getCheckSetId() != NO_CHECK_SET) {
943             bundle.putBoolean(key, isChecked());
944         }
945     }
946 
947     /**
948      * Restore action from a bundle using a given key. When isAutoRestore() is true:
949      * <ul>
950      * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
951      * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
952      * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
953      * <li>{@link GuidedDatePickerAction} will be saved</li>
954      * </ul>
955      * Subclass may override this method.
956      * @param bundle  Bundle to restore the Action from.
957      * @param key Key used to restore the Action.
958      */
onRestoreInstanceState(@onNull Bundle bundle, @NonNull String key)959     public void onRestoreInstanceState(@NonNull Bundle bundle, @NonNull String key) {
960         if (needAutoSaveTitle()) {
961             String title = bundle.getString(key);
962             if (title != null) {
963                 setTitle(title);
964             }
965         } else if (needAutoSaveDescription()) {
966             String description = bundle.getString(key);
967             if (description != null) {
968                 setDescription(description);
969             }
970         } else if (getCheckSetId() != NO_CHECK_SET) {
971             setChecked(bundle.getBoolean(key, isChecked()));
972         }
973     }
974 
isPasswordVariant(int inputType)975     static boolean isPasswordVariant(int inputType) {
976         final int variation = inputType & InputType.TYPE_MASK_VARIATION;
977         return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
978                 || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
979                 || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
980     }
981 
needAutoSaveTitle()982     final boolean needAutoSaveTitle() {
983         return isEditable() && !isPasswordVariant(getEditInputType());
984     }
985 
needAutoSaveDescription()986     final boolean needAutoSaveDescription() {
987         return isDescriptionEditable() && !isPasswordVariant(getDescriptionEditInputType());
988     }
989 
990 }
991