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