GuidedAction.java revision a7f9f2928b60143e09f216a10ea50f2dd4581170
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.support.v17.leanback.R;
17
18import android.content.Context;
19import android.content.Intent;
20import android.graphics.drawable.Drawable;
21import android.text.InputType;
22import android.util.Log;
23
24import java.util.List;
25
26/**
27 * A data class which represents an action within a {@link
28 * android.support.v17.leanback.app.GuidedStepFragment}. GuidedActions contain at minimum a title
29 * and a description, and typically also an icon.
30 * <p>
31 * A GuidedAction typically represents a single action a user may take, but may also represent a
32 * possible choice out of a group of mutually exclusive choices (similar to radio buttons), or an
33 * information-only label (in which case the item cannot be clicked).
34 * <p>
35 * GuidedActions may optionally be checked. They may also indicate that they will request further
36 * user input on selection, in which case they will be displayed with a chevron indicator.
37 * <p>
38 * GuidedAction recommends to use {@link Builder}. When application subclass GuidedAction, it
39 * can subclass {@link BuilderBase}, implement {@link BuilderBase#build()} where it should
40 * call {@link BuilderBase#applyValues(GuidedAction)}.
41 */
42public class GuidedAction extends Action {
43
44    private static final String TAG = "GuidedAction";
45
46    /**
47     * Special check set Id that is neither checkbox nor radio.
48     */
49    public static final int NO_CHECK_SET = 0;
50    /**
51     * Default checkset Id for radio.
52     */
53    public static final int DEFAULT_CHECK_SET_ID = 1;
54    /**
55     * Checkset Id for checkbox.
56     */
57    public static final int CHECKBOX_CHECK_SET_ID = -1;
58
59    /**
60     * When finishing editing, goes to next action.
61     */
62    public static final long ACTION_ID_NEXT = -2;
63    /**
64     * When finishing editing, stay on current action.
65     */
66    public static final long ACTION_ID_CURRENT = -3;
67
68    /**
69     * Id of standard OK action.
70     */
71    public static final long ACTION_ID_OK = -4;
72
73    /**
74     * Id of standard Cancel action.
75     */
76    public static final long ACTION_ID_CANCEL = -5;
77
78    /**
79     * Id of standard Finish action.
80     */
81    public static final long ACTION_ID_FINISH = -6;
82
83    /**
84     * Id of standard Finish action.
85     */
86    public static final long ACTION_ID_CONTINUE = -7;
87
88    /**
89     * Id of standard Yes action.
90     */
91    public static final long ACTION_ID_YES = -8;
92
93    /**
94     * Id of standard No action.
95     */
96    public static final long ACTION_ID_NO = -9;
97
98    /**
99     * Base builder class to build a {@link GuidedAction} object.  When subclass GuidedAction, you
100     * can override this BuilderBase class, implements {@link #build()} and call
101     * {@link #applyValues(GuidedAction)}.  When using GuidedAction directly, use {@link Builder}.
102     */
103    public abstract static class BuilderBase<T extends GuidedAction> {
104        private long mId;
105        private CharSequence mTitle;
106        private CharSequence mEditTitle;
107        private CharSequence mDescription;
108        private CharSequence mEditDescription;
109        private Drawable mIcon;
110        private boolean mChecked;
111        private boolean mMultilineDescription;
112        private boolean mHasNext;
113        private boolean mInfoOnly;
114        private boolean mEditable = false;
115        private boolean mDescriptionEditable = false;
116        private int mInputType = InputType.TYPE_CLASS_TEXT;
117        private int mDescriptionInputType = InputType.TYPE_CLASS_TEXT;
118        private int mEditInputType = InputType.TYPE_CLASS_TEXT;
119        private int mDescriptionEditInputType = InputType.TYPE_CLASS_TEXT;
120        private int mCheckSetId = NO_CHECK_SET;
121        private boolean mEnabled = true;
122        private boolean mFocusable = true;
123        private List<GuidedAction> mSubActions;
124        private Intent mIntent;
125
126        /**
127         * Builds the GuidedAction corresponding to this Builder.
128         * @return the GuidedAction as configured through this BuilderBase.
129         */
130        public abstract T build();
131
132        /**
133         * Subclass of BuilderBase should call this function to apply values.
134         * @param action GuidedAction to apply BuilderBase values.
135         */
136        protected final void applyValues(GuidedAction action) {
137            // Base Action values
138            action.setId(mId);
139            action.setLabel1(mTitle);
140            action.setEditTitle(mEditTitle);
141            action.setLabel2(mDescription);
142            action.setEditDescription(mEditDescription);
143            action.setIcon(mIcon);
144
145            // Subclass values
146            action.mIntent = mIntent;
147            action.mEditable = mEditable;
148            action.mDescriptionEditable = mDescriptionEditable;
149            action.mInputType = mInputType;
150            action.mDescriptionInputType = mDescriptionInputType;
151            action.mEditInputType = mEditInputType;
152            action.mDescriptionEditInputType = mDescriptionEditInputType;
153            action.mChecked = mChecked;
154            action.mCheckSetId = mCheckSetId;
155            action.mMultilineDescription = mMultilineDescription;
156            action.mHasNext = mHasNext;
157            action.mInfoOnly = mInfoOnly;
158            action.mEnabled = mEnabled;
159            action.mFocusable = mFocusable;
160            action.mSubActions = mSubActions;
161        }
162
163        /**
164         * Construct a standard "OK" action with {@link GuidedAction#ACTION_ID_OK}.
165         * @param context Context for loading action title.
166         * @return The same BuilderBase object.
167         */
168        public BuilderBase<T> constructOK(Context context) {
169            mId = ACTION_ID_OK;
170            mTitle = context.getString(android.R.string.ok);
171            return this;
172        }
173
174        /**
175         * Construct a standard "Cancel" action with {@link GuidedAction#ACTION_ID_CANCEL}.
176         * @param context Context for loading action title.
177         * @return The same BuilderBase object.
178         */
179        public BuilderBase<T> constructCancel(Context context) {
180            mId = ACTION_ID_CANCEL;
181            mTitle = context.getString(android.R.string.cancel);
182            return this;
183        }
184
185        /**
186         * Construct a standard "Finish" action with {@link GuidedAction#ACTION_ID_FINISH}.
187         * @param context Context for loading action title.
188         * @return The same BuilderBase object.
189         */
190        public BuilderBase<T> constructFinish(Context context) {
191            mId = ACTION_ID_FINISH;
192            mTitle = context.getString(R.string.lb_guidedaction_finish_title);
193            return this;
194        }
195
196        /**
197         * Construct a standard "Continue" action with {@link GuidedAction#ACTION_ID_CONTINUE}.
198         * @param context Context for loading action title.
199         * @return The same BuilderBase object.
200         */
201        public BuilderBase<T> constructContinue(Context context) {
202            mId = ACTION_ID_CONTINUE;
203            mHasNext = true;
204            mTitle = context.getString(R.string.lb_guidedaction_continue_title);
205            return this;
206        }
207
208        /**
209         * Construct a standard "Yes" action with {@link GuidedAction#ACTION_ID_YES}.
210         * @param context Context for loading action title.
211         * @return The same BuilderBase object.
212         */
213        public BuilderBase<T> constructYes(Context context) {
214            mId = ACTION_ID_YES;
215            mTitle = context.getString(android.R.string.yes);
216            return this;
217        }
218
219        /**
220         * Construct a standard "No" action with {@link GuidedAction#ACTION_ID_NO}.
221         * @param context Context for loading action title.
222         * @return The same BuilderBase object.
223         */
224        public BuilderBase<T> constructNo(Context context) {
225            mId = ACTION_ID_NO;
226            mTitle = context.getString(android.R.string.no);
227            return this;
228        }
229
230        /**
231         * Sets the ID associated with this action.  The ID can be any value the client wishes;
232         * it is typically used to determine what to do when an action is clicked.
233         * @param id The ID to associate with this action.
234         */
235        public BuilderBase<T> id(long id) {
236            mId = id;
237            return this;
238        }
239
240        /**
241         * Sets the title for this action.  The title is typically a short string indicating the
242         * action to be taken on click, e.g. "Continue" or "Cancel".
243         * @param title The title for this action.
244         */
245        public BuilderBase<T> title(CharSequence title) {
246            mTitle = title;
247            return this;
248        }
249
250        /**
251         * Sets the optional title text to edit.  When TextView is activated, the edit title
252         * replaces the string of title.
253         */
254        public BuilderBase<T> editTitle(CharSequence editTitle) {
255            mEditTitle = editTitle;
256            return this;
257        }
258
259        /**
260         * Sets the description for this action.  The description is typically a longer string
261         * providing extra information on what the action will do.
262         * @param description The description for this action.
263         */
264        public BuilderBase<T> description(CharSequence description) {
265            mDescription = description;
266            return this;
267        }
268
269        /**
270         * Sets the optional description text to edit.  When TextView is activated, the edit
271         * description replaces the string of description.
272         * @param description The description to edit for this action.
273         */
274        public BuilderBase<T> editDescription(CharSequence description) {
275            mEditDescription = description;
276            return this;
277        }
278
279        /**
280         * Sets the intent associated with this action.  Clients would typically fire this intent
281         * directly when the action is clicked.
282         * @param intent The intent associated with this action.
283         */
284        public BuilderBase<T> intent(Intent intent) {
285            mIntent = intent;
286            return this;
287        }
288
289        /**
290         * Sets the action's icon drawable.
291         * @param icon The drawable for the icon associated with this action.
292         */
293        public BuilderBase<T> icon(Drawable icon) {
294            mIcon = icon;
295            return this;
296        }
297
298        /**
299         * Sets the action's icon drawable by retrieving it by resource ID from the specified
300         * context. This is a convenience function that simply looks up the drawable and calls
301         * {@link #icon}.
302         * @param iconResourceId The resource ID for the icon associated with this action.
303         * @param context The context whose resource ID should be retrieved.
304         */
305        public BuilderBase<T> iconResourceId(int iconResourceId, Context context) {
306            return icon(context.getResources().getDrawable(iconResourceId));
307        }
308
309        /**
310         * Indicates whether this action title is editable. Note: Editable actions cannot also be
311         * checked, or belong to a check set.
312         * @param editable Whether this action is editable.
313         */
314        public BuilderBase<T> editable(boolean editable) {
315            mEditable = editable;
316            if (mChecked || mCheckSetId != NO_CHECK_SET) {
317                throw new IllegalArgumentException("Editable actions cannot also be checked");
318            }
319            return this;
320        }
321
322        /**
323         * Indicates whether this action's description is editable
324         * @param editable Whether this action description is editable.
325         */
326        public BuilderBase<T> descriptionEditable(boolean editable) {
327            mDescriptionEditable = editable;
328            if (mChecked || mCheckSetId != NO_CHECK_SET) {
329                throw new IllegalArgumentException("Editable actions cannot also be checked");
330            }
331            return this;
332        }
333
334        /**
335         * Sets {@link InputType} of this action title not in editing.
336         *
337         * @param inputType InputType for the action title not in editing.
338         */
339        public BuilderBase<T> inputType(int inputType) {
340            mInputType = inputType;
341            return this;
342        }
343
344        /**
345         * Sets {@link InputType} of this action description not in editing.
346         *
347         * @param inputType InputType for the action description not in editing.
348         */
349        public BuilderBase<T> descriptionInputType(int inputType) {
350            mDescriptionInputType = inputType;
351            return this;
352        }
353
354
355        /**
356         * Sets {@link InputType} of this action title in editing.
357         *
358         * @param inputType InputType for the action title in editing.
359         */
360        public BuilderBase<T> editInputType(int inputType) {
361            mEditInputType = inputType;
362            return this;
363        }
364
365        /**
366         * Sets {@link InputType} of this action description in editing.
367         *
368         * @param inputType InputType for the action description in editing.
369         */
370        public BuilderBase<T> descriptionEditInputType(int inputType) {
371            mDescriptionEditInputType = inputType;
372            return this;
373        }
374
375
376        /**
377         * Indicates whether this action is initially checked.
378         * @param checked Whether this action is checked.
379         */
380        public BuilderBase<T> checked(boolean checked) {
381            mChecked = checked;
382            if (mEditable || mDescriptionEditable) {
383                throw new IllegalArgumentException("Editable actions cannot also be checked");
384            }
385            return this;
386        }
387
388        /**
389         * Indicates whether this action is part of a single-select group similar to radio buttons
390         * or this action is a checkbox. When one item in a check set is checked, all others with
391         * the same check set ID will be nchecked automatically.
392         * @param checkSetId The check set ID, or {@link GuidedAction#NO_CHECK_SET} to indicate not
393         * radio or checkbox, or {@link GuidedAction#CHECKBOX_CHECK_SET_ID} to indicate a checkbox.
394         */
395        public BuilderBase<T> checkSetId(int checkSetId) {
396            mCheckSetId = checkSetId;
397            if (mEditable || mDescriptionEditable) {
398                throw new IllegalArgumentException("Editable actions cannot also be in check sets");
399            }
400            return this;
401        }
402
403        /**
404         * Indicates whether the title and description are long, and should be displayed
405         * appropriately.
406         * @param multilineDescription Whether this action has a multiline description.
407         */
408        public BuilderBase<T> multilineDescription(boolean multilineDescription) {
409            mMultilineDescription = multilineDescription;
410            return this;
411        }
412
413        /**
414         * Indicates whether this action has a next state and should display a chevron.
415         * @param hasNext Whether this action has a next state.
416         */
417        public BuilderBase<T> hasNext(boolean hasNext) {
418            mHasNext = hasNext;
419            return this;
420        }
421
422        /**
423         * Indicates whether this action is for information purposes only and cannot be clicked.
424         * @param infoOnly Whether this action has a next state.
425         */
426        public BuilderBase<T> infoOnly(boolean infoOnly) {
427            mInfoOnly = infoOnly;
428            return this;
429        }
430
431        /**
432         * Indicates whether this action is enabled.  If not enabled, an action cannot be clicked.
433         * @param enabled Whether the action is enabled.
434         */
435        public BuilderBase<T> enabled(boolean enabled) {
436            mEnabled = enabled;
437            return this;
438        }
439
440        /**
441         * Indicates whether this action can take focus.
442         * @param focusable
443         * @return The same BuilderBase object.
444         */
445        public BuilderBase<T> focusable(boolean focusable) {
446            mFocusable = focusable;
447            return this;
448        }
449
450        /**
451         * Sets sub actions list.
452         * @param subActions
453         * @return The same BuilderBase object.
454         */
455        public BuilderBase<T> subActions(List<GuidedAction> subActions) {
456            mSubActions = subActions;
457            return this;
458        }
459    }
460
461    /**
462     * Builds a {@link GuidedAction} object.
463     */
464    public static class Builder extends BuilderBase<GuidedAction> {
465
466        @Override
467        public GuidedAction build() {
468            GuidedAction action = new GuidedAction();
469            applyValues(action);
470            return action;
471        }
472
473    }
474
475    private CharSequence mEditTitle;
476    private CharSequence mEditDescription;
477    private boolean mEditable;
478    private boolean mDescriptionEditable;
479    private int mInputType;
480    private int mDescriptionInputType;
481    private int mEditInputType;
482    private int mDescriptionEditInputType;
483    private boolean mMultilineDescription;
484    private boolean mHasNext;
485    private boolean mChecked;
486    private boolean mInfoOnly;
487    private int mCheckSetId;
488    private boolean mEnabled;
489    private boolean mFocusable;
490    private List<GuidedAction> mSubActions;
491
492    private Intent mIntent;
493
494    protected GuidedAction() {
495        super(0);
496    }
497
498    /**
499     * Returns the title of this action.
500     * @return The title set when this action was built.
501     */
502    public CharSequence getTitle() {
503        return getLabel1();
504    }
505
506    /**
507     * Sets the title of this action.
508     * @param title The title set when this action was built.
509     */
510    public void setTitle(CharSequence title) {
511        setLabel1(title);
512    }
513
514    /**
515     * Returns the optional title text to edit.  When not null, it is being edited instead of
516     * {@link #getTitle()}.
517     * @return Optional title text to edit instead of {@link #getTitle()}.
518     */
519    public CharSequence getEditTitle() {
520        return mEditTitle;
521    }
522
523    /**
524     * Sets the optional title text to edit instead of {@link #setTitle(CharSequence)}.
525     * @param editTitle Optional title text to edit instead of {@link #setTitle(CharSequence)}.
526     */
527    public void setEditTitle(CharSequence editTitle) {
528        mEditTitle = editTitle;
529    }
530
531    /**
532     * Returns the optional description text to edit.  When not null, it is being edited instead of
533     * {@link #getDescription()}.
534     * @return Optional description text to edit instead of {@link #getDescription()}.
535     */
536    public CharSequence getEditDescription() {
537        return mEditDescription;
538    }
539
540    /**
541     * Sets the optional description text to edit instead of {@link #setDescription(CharSequence)}.
542     * @param editDescription Optional description text to edit instead of
543     * {@link #setDescription(CharSequence)}.
544     */
545    public void setEditDescription(CharSequence editDescription) {
546        mEditDescription = editDescription;
547    }
548
549    /**
550     * Returns true if {@link #getEditTitle()} is not null.  When true, the {@link #getEditTitle()}
551     * is being edited instead of {@link #getTitle()}.
552     * @return true if {@link #getEditTitle()} is not null.
553     */
554    public boolean isEditTitleUsed() {
555        return mEditTitle != null;
556    }
557
558    /**
559     * Returns the description of this action.
560     * @return The description of this action.
561     */
562    public CharSequence getDescription() {
563        return getLabel2();
564    }
565
566    /**
567     * Sets the description of this action.
568     * @param description The description of the action.
569     */
570    public void setDescription(CharSequence description) {
571        setLabel2(description);
572    }
573
574    /**
575     * Returns the intent associated with this action.
576     * @return The intent set when this action was built.
577     */
578    public Intent getIntent() {
579        return mIntent;
580    }
581
582    /**
583     * Returns whether this action title is editable.
584     * @return true if the action title is editable, false otherwise.
585     */
586    public boolean isEditable() {
587        return mEditable;
588    }
589
590    /**
591     * Returns whether this action description is editable.
592     * @return true if the action description is editable, false otherwise.
593     */
594    public boolean isDescriptionEditable() {
595        return mDescriptionEditable;
596    }
597
598    /**
599     * Returns InputType of action title in editing; only valid when {@link #isEditable()} is true.
600     * @return InputType of action title in editing.
601     */
602    public int getEditInputType() {
603        return mEditInputType;
604    }
605
606    /**
607     * Returns InputType of action description in editing; only valid when
608     * {@link #isDescriptionEditable()} is true.
609     * @return InputType of action description in editing.
610     */
611    public int getDescriptionEditInputType() {
612        return mDescriptionEditInputType;
613    }
614
615    /**
616     * Returns InputType of action title not in editing.
617     * @return InputType of action title not in editing.
618     */
619    public int getInputType() {
620        return mInputType;
621    }
622
623    /**
624     * Returns InputType of action description not in editing.
625     * @return InputType of action description not in editing.
626     */
627    public int getDescriptionInputType() {
628        return mDescriptionInputType;
629    }
630
631    /**
632     * Returns whether this action is checked.
633     * @return true if the action is currently checked, false otherwise.
634     */
635    public boolean isChecked() {
636        return mChecked;
637    }
638
639    /**
640     * Sets whether this action is checked.
641     * @param checked Whether this action should be checked.
642     */
643    public void setChecked(boolean checked) {
644        mChecked = checked;
645    }
646
647    /**
648     * Returns the check set id this action is a part of. All actions in the same list with the same
649     * check set id are considered linked. When one of the actions within that set is selected, that
650     * action becomes checked, while all the other actions become unchecked.
651     *
652     * @return an integer representing the check set this action is a part of, or
653     *         {@link #CHECKBOX_CHECK_SET_ID} if this is a checkbox, or {@link #NO_CHECK_SET} if
654     *         this action is not a checkbox or radiobutton.
655     */
656    public int getCheckSetId() {
657        return mCheckSetId;
658    }
659
660    /**
661     * Returns whether this action is has a multiline description.
662     * @return true if the action was constructed as having a multiline description, false
663     * otherwise.
664     */
665    public boolean hasMultilineDescription() {
666        return mMultilineDescription;
667    }
668
669    /**
670     * Returns whether this action is enabled.
671     * @return true if the action is currently enabled, false otherwise.
672     */
673    public boolean isEnabled() {
674        return mEnabled;
675    }
676
677    /**
678     * Sets whether this action is enabled.
679     * @param enabled Whether this action should be enabled.
680     */
681    public void setEnabled(boolean enabled) {
682        mEnabled = enabled;
683    }
684
685    /**
686     * Returns whether this action is focusable.
687     * @return true if the action is currently focusable, false otherwise.
688     */
689    public boolean isFocusable() {
690        return mFocusable;
691    }
692
693    /**
694     * Sets whether this action is focusable.
695     * @param focusable Whether this action should be focusable.
696     */
697    public void setFocusable(boolean focusable) {
698        mFocusable = focusable;
699    }
700
701    /**
702     * Returns whether this action will request further user input when selected, such as showing
703     * another GuidedStepFragment or launching a new activity. Configured during construction.
704     * @return true if the action will request further user input when selected, false otherwise.
705     */
706    public boolean hasNext() {
707        return mHasNext;
708    }
709
710    /**
711     * Returns whether the action will only display information and is thus not clickable. If both
712     * this and {@link #hasNext()} are true, infoOnly takes precedence. The default is false. For
713     * example, this might represent e.g. the amount of storage a document uses, or the cost of an
714     * app.
715     * @return true if will only display information, false otherwise.
716     */
717    public boolean infoOnly() {
718        return mInfoOnly;
719    }
720
721    /**
722     * Change sub actions list.
723     * @param actions Sub actions list to set on this action.  Sets null to disable sub actions.
724     */
725    public void setSubActions(List<GuidedAction> actions) {
726        mSubActions = actions;
727    }
728
729    /**
730     * @return List of sub actions or null if sub actions list is not enabled.
731     */
732    public List<GuidedAction> getSubActions() {
733        return mSubActions;
734    }
735
736    /**
737     * @return True if has sub actions list, even it's currently empty.
738     */
739    public boolean hasSubActions() {
740        return mSubActions != null;
741    }
742
743}
744