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