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