• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.settings.dialog;
18 
19 import android.content.Intent;
20 import android.content.res.Resources;
21 import android.graphics.drawable.Drawable;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * A data class which represents a settings layout within an
34  * {@link SettingsLayoutFragment}. Represents a list of choices the
35  * user can make, a radio-button list of configuration options, or just a
36  * list of information.
37  */
38 public class Layout implements Parcelable {
39 
40     public interface LayoutNodeRefreshListener {
onRefreshView()41         void onRefreshView();
getSelectedNode()42         Node getSelectedNode();
43     }
44 
45     public interface ContentNodeRefreshListener {
onRefreshView()46         void onRefreshView();
47     }
48 
49     public interface Node {
getTitle()50         String getTitle();
51     }
52 
53     private abstract static class LayoutTreeNode implements Node {
54         LayoutTreeBranch mParent;
55 
isEnabled()56         /* package */ boolean isEnabled() {
57             return false;
58         }
59 
getAppearance()60         /* package */ abstract Appearance getAppearance();
Log(int level)61         /* package */ abstract void Log(int level);
62     }
63 
64     private abstract static class LayoutTreeBranch extends LayoutTreeNode {
65         final ArrayList<LayoutTreeNode> mChildren;
LayoutTreeBranch()66         LayoutTreeBranch() {
67             mChildren = new ArrayList<>();
68         }
69     }
70 
71     public static class LayoutRow {
72         public static final int NO_CHECK_SET = 0;
73         public static final int VIEW_TYPE_ACTION = 0;
74         public static final int VIEW_TYPE_STATIC = 1;
75         public static final int VIEW_TYPE_WALLOFTEXT = 2;
76 
77         private String mTitle;
78         private StringGetter mDescription;
79         private final LayoutTreeNode mNode;
80         private final boolean mEnabled;
81         private final int mViewType;
82         private BooleanGetter mChecked = FALSE_GETTER;
83         private Drawable mIcon = null;
84         private int mSelectionIndex;
85 
getNode()86         public Node getNode() {
87             return mNode;
88         }
89 
getIconUri()90         public Uri getIconUri() {
91             return null;
92         }
93 
getIcon()94         public Drawable getIcon() {
95             return mIcon;
96         }
97 
getCheckSetId()98         public int getCheckSetId() {
99             return 0;
100         }
101 
isChecked()102         public boolean isChecked() {
103             return mChecked.get();
104         }
105 
getChecked()106         public BooleanGetter getChecked() {
107             return mChecked;
108         }
109 
setChecked(boolean v)110         public void setChecked(boolean v) {
111             mChecked = v ? TRUE_GETTER : FALSE_GETTER;
112         }
113 
infoOnly()114         public boolean infoOnly() {
115             return false;
116         }
117 
isEnabled()118         public boolean isEnabled() {
119             return mEnabled;
120         }
121 
hasNext()122         public boolean hasNext() {
123             return false;
124         }
125 
getTitle()126         public String getTitle() {
127             return mTitle;
128         }
129 
getDescription()130         public StringGetter getDescription() {
131             return mDescription;
132         }
133 
getViewType()134         public int getViewType() {
135             return mViewType;
136         }
137 
isRadio()138         public boolean isRadio() {
139             return mNode instanceof SelectionGroup;
140         }
141 
getRadioId()142         public int getRadioId() {
143             return ((SelectionGroup) mNode).getId(mSelectionIndex);
144         }
145 
setRadioSelectedIndex()146         public boolean setRadioSelectedIndex() {
147             return ((SelectionGroup) mNode).setSelectedIndex(mSelectionIndex);
148         }
149 
isGoBack()150         public boolean isGoBack() {
151             if (mNode instanceof Action) {
152                 Action a = (Action) mNode;
153                 if (a.mActionId == Action.ACTION_BACK) {
154                     return true;
155                 }
156             }
157             return false;
158         }
159 
getUserAction()160         public Action getUserAction() {
161             if (mNode instanceof Action) {
162                 Action a = (Action) mNode;
163                 if (a.mActionId != Action.ACTION_NONE) {
164                     return a;
165                 }
166             }
167             return null;
168         }
169 
getContentIconRes()170         public int getContentIconRes() {
171             if (mNode instanceof Header) {
172                 return ((Header) mNode).mContentIconRes;
173             }
174             return 0;
175         }
176 
LayoutRow(LayoutTreeNode node)177         public LayoutRow(LayoutTreeNode node) {
178             mNode = node;
179             if (node instanceof Static) {
180                 mViewType = VIEW_TYPE_STATIC;
181                 mTitle = ((Static) node).mTitle;
182             } else if (node instanceof WallOfText) {
183                 mViewType = VIEW_TYPE_WALLOFTEXT;
184                 mTitle = ((WallOfText) node).mTitle;
185             } else {
186                 mViewType = VIEW_TYPE_ACTION;
187             }
188             mEnabled = node.isEnabled();
189             final Appearance a = node.getAppearance();
190             if (a != null) {
191                 mTitle = a.getTitle();
192                 mDescription = a.mDescriptionGetter;
193                 mIcon = a.getIcon();
194                 mChecked = a.getChecked();
195             }
196         }
197 
LayoutRow(final SelectionGroup selectionGroup, int selectedIndex)198         public LayoutRow(final SelectionGroup selectionGroup, int selectedIndex) {
199             mNode = selectionGroup;
200             mViewType = VIEW_TYPE_ACTION;
201             mSelectionIndex = selectedIndex;
202             mTitle = selectionGroup.getTitle(selectedIndex);
203             mChecked = selectionGroup.getChecked(selectedIndex) ? TRUE_GETTER : FALSE_GETTER;
204             mDescription = selectionGroup.getDescription(selectedIndex);
205             mEnabled = true;
206         }
207     }
208 
209     /**
210      * Getter object for boolean values, mostly used for checked state on headers/actions/etc.
211      * This is a slightly simpler alternative to using a LayoutGetter to make sure the checked state
212      * is always up to date.
213      */
214     public abstract static class BooleanGetter {
215         private ContentNodeRefreshListener mListener;
216 
setListener(ContentNodeRefreshListener listener)217         public void setListener(ContentNodeRefreshListener listener) {
218             mListener = listener;
219         }
220 
get()221         public abstract boolean get();
222 
223         /**
224          * Notification from client that antecedent data has changed and the string should be
225          * redisplayed.
226          */
refreshView()227         public void refreshView() {
228             if (mListener != null) {
229                 mListener.onRefreshView();
230             }
231         }
232     }
233 
234     /**
235      * Basic mutable implementation of BooleanGetter. Call {@link MutableBooleanGetter#set(boolean)}
236      * to modify the state and automatically refresh the view.
237      */
238     public final static class MutableBooleanGetter extends BooleanGetter {
239         private boolean mState;
240 
MutableBooleanGetter()241         public MutableBooleanGetter() {
242             mState = false;
243         }
244 
MutableBooleanGetter(boolean state)245         public MutableBooleanGetter(boolean state) {
246             mState = state;
247         }
248 
249         @Override
get()250         public boolean get() {
251             return mState;
252         }
253 
254         /**
255          * Set the boolean value for this object. Automatically calls {@link #refreshView()}
256          * @param newState State to set
257          */
set(boolean newState)258         public void set(boolean newState) {
259             mState = newState;
260             refreshView();
261         }
262     }
263 
264     private final static class LiteralBooleanGetter extends BooleanGetter {
265         private final boolean mState;
266 
LiteralBooleanGetter(boolean state)267         public LiteralBooleanGetter(boolean state) {
268             mState = state;
269         }
270 
271         @Override
get()272         public boolean get() {
273             return mState;
274         }
275 
276         @Override
setListener(ContentNodeRefreshListener listener)277         public void setListener(ContentNodeRefreshListener listener) {}
278 
279         @Override
refreshView()280         public void refreshView() {}
281     }
282 
283     private static final BooleanGetter TRUE_GETTER = new LiteralBooleanGetter(true);
284     private static final BooleanGetter FALSE_GETTER = new LiteralBooleanGetter(false);
285 
286     public abstract static class DrawableGetter {
get()287         public abstract Drawable get();
288 
289         /**
290          * Notification from client that antecedent data has changed and the drawable should be
291          * redisplayed.
292          */
refreshView()293         public void refreshView() {
294             //TODO - When implementing, ensure that multiple updates from the same event do not
295             // cause multiple view updates.
296         }
297     }
298 
299     public abstract static class StringGetter {
300         private ContentNodeRefreshListener mListener;
301 
setListener(ContentNodeRefreshListener listener)302         public void setListener(ContentNodeRefreshListener listener) {
303             mListener = listener;
304         }
305 
get()306         public abstract String get();
307 
308         /**
309          * Notification from client that antecedent data has changed and the string should be
310          * redisplayed.
311          */
refreshView()312         public void refreshView() {
313             if (mListener != null) {
314                 mListener.onRefreshView();
315             }
316         }
317     }
318 
319     /**
320      * Implementation of "StringGetter" that stores and returns a literal string.
321      */
322     private static class LiteralStringGetter extends StringGetter {
323         private final String mValue;
get()324         public String get() {
325             return mValue;
326         }
LiteralStringGetter(String value)327         LiteralStringGetter(String value) {
328             mValue = value;
329         }
330     }
331 
332     /**
333      * Implementation of "StringGetter" that stores a string resource id and returns a string.
334      */
335     private static class ResourceStringGetter extends StringGetter {
336         private final int mStringResourceId;
337         private final Resources mRes;
get()338         public String get() {
339             return mRes.getString(mStringResourceId);
340         }
ResourceStringGetter(Resources res, int stringResourceId)341         ResourceStringGetter(Resources res, int stringResourceId) {
342             mRes = res;
343             mStringResourceId = stringResourceId;
344         }
345     }
346 
347     public abstract static class LayoutGetter extends LayoutTreeNode {
348         // Layout manages this listener; removing it when this node is not visible and setting it
349         // when it is.  Users are expected to set the listener with Layout.setRefreshViewListener.
350         private LayoutNodeRefreshListener mListener;
351 
setListener(LayoutNodeRefreshListener listener)352         public void setListener(LayoutNodeRefreshListener listener) {
353             mListener = listener;
354         }
355 
notVisible()356         public void notVisible() {
357             mListener = null;
358         }
359 
get()360         public abstract Layout get();
361 
getSelectedNode()362         public Node getSelectedNode() {
363             if (mListener != null) {
364                 return mListener.getSelectedNode();
365             } else {
366                 return null;
367             }
368         }
369 
370         /**
371          * Notification from client that antecedent data has changed and the list containing the
372          * contents of this getter should be updated.
373          */
refreshView()374         public void refreshView() {
375             if (mListener != null) {
376                 mListener.onRefreshView();
377             }
378         }
379 
380         @Override
getTitle()381         public String getTitle() {
382             return null;
383         }
384 
385         @Override
getAppearance()386         /* package */ Appearance getAppearance() {
387             return null;
388         }
389 
390         @Override
Log(int level)391         /* package */ void Log(int level) {
392             Log.d("Layout", indent(level) + "LayoutGetter");
393             Layout l = get();
394             l.Log(level + 1);
395         }
396     }
397 
398     /**
399      * A list of "select one of" radio style buttons.
400      */
401     public static class SelectionGroup extends LayoutTreeNode {
402         final String mTitle[];
403         final StringGetter mDescription[];
404         final int mId[];
405         int mSelectedIndex;
406 
407         public static final class Builder {
408             private static class Item {
409                 public final String title;
410                 public final StringGetter description;
411                 public final int id;
412 
Item(String title, String description, int id)413                 public Item(String title, String description, int id) {
414                     this.title = title;
415                     if (TextUtils.isEmpty(description)) {
416                         this.description = null;
417                     } else {
418                         this.description = new LiteralStringGetter(description);
419                     }
420                     this.id = id;
421                 }
422             }
423 
424             private final List<Item> mItems;
425             private boolean mSetInitialId = false;
426             private int mInitialId;
427 
Builder()428             public Builder() {
429                 mItems = new ArrayList<>();
430             }
431 
Builder(int count)432             public Builder(int count) {
433                 mItems = new ArrayList<>(count);
434             }
435 
add(String title, String description, int id)436             public Builder add(String title, String description, int id) {
437                 mItems.add(new Item(title, description, id));
438                 return this;
439             }
440 
select(int id)441             public Builder select(int id) {
442                 mSetInitialId = true;
443                 mInitialId = id;
444                 return this;
445             }
446 
build()447             public SelectionGroup build() {
448                 final int size = mItems.size();
449                 final String[] titles = new String[size];
450                 final StringGetter[] descriptions = new StringGetter[size];
451                 final int[] ids = new int[size];
452                 int i = 0;
453                 for (final Item item : mItems) {
454                     titles[i] = item.title;
455                     descriptions[i] = item.description;
456                     ids[i] = item.id;
457                     i++;
458                 }
459                 final SelectionGroup selectionGroup = new SelectionGroup(titles, descriptions, ids);
460                 if (mSetInitialId) {
461                     selectionGroup.setSelected(mInitialId);
462                 }
463                 return selectionGroup;
464             }
465         }
466 
SelectionGroup(Resources res, int param[][])467         public SelectionGroup(Resources res, int param[][]) {
468             mSelectedIndex = -1;
469             mTitle = new String[param.length];
470             mDescription = new StringGetter[param.length];
471             mId = new int[param.length];
472             for (int i = 0; i < param.length; ++i) {
473                 mTitle[i] = res.getString(param[i][0]);
474                 mId[i] = param[i][1];
475             }
476         }
477 
SelectionGroup(String[] titles, StringGetter[] descriptions, int[] ids)478         public SelectionGroup(String[] titles, StringGetter[] descriptions, int[] ids) {
479             mSelectedIndex = -1;
480             mTitle = titles;
481             mDescription = descriptions;
482             mId = ids;
483         }
484 
485         @Override
getTitle()486         public String getTitle() {
487             if (mSelectedIndex >= 0 && mSelectedIndex < mTitle.length) {
488                 return mTitle[mSelectedIndex];
489             } else {
490                 return "";
491             }
492         }
493 
494         @Override
getAppearance()495         /* package */ Appearance getAppearance() {
496             return null;
497         }
498 
499         @Override
Log(int level)500         /* package */ void Log(int level) {
501             Log.d("Layout", indent(level) + "SelectionGroup  '" + getTitle() + "'");
502         }
503 
getTitle(int index)504         public String getTitle(int index) {
505             return mTitle[index];
506         }
507 
getDescription(int index)508         public StringGetter getDescription(int index) {
509             return mDescription[index];
510         }
511 
getChecked(int index)512         public boolean getChecked(int index) {
513             return mSelectedIndex == index;
514         }
515 
size()516         public int size() {
517             return mTitle.length;
518         }
519 
setSelected(int id)520         public void setSelected(int id) {
521             mSelectedIndex = -1;
522             for (int index = 0, dim = mId.length; index < dim; ++index) {
523                 if (mId[index] == id) {
524                     mSelectedIndex = index;
525                     break;
526                 }
527             }
528         }
529 
setSelectedIndex(int selectedIndex)530         public boolean setSelectedIndex(int selectedIndex) {
531             if (mSelectedIndex != selectedIndex && selectedIndex < mId.length) {
532                 mSelectedIndex = selectedIndex;
533                 return true;
534             } else {
535                 return false;
536             }
537         }
538 
getId(int index)539         public int getId(int index) {
540             return mId[index];
541         }
542 
getId()543         public int getId() {
544             return mId[mSelectedIndex];
545         }
546     }
547 
548     /**
549      * Implementation of "StringGetter" that returns a string describing the currently selected
550      * item in a SelectionGroup.
551      */
552     public static class SelectionGroupStringGetter extends StringGetter {
553 
554         private final SelectionGroup mSelectionGroup;
555 
556         @Override
get()557         public String get() {
558             return mSelectionGroup.getTitle();
559         }
560 
SelectionGroupStringGetter(SelectionGroup selectionGroup)561         public SelectionGroupStringGetter(SelectionGroup selectionGroup) {
562             mSelectionGroup = selectionGroup;
563         }
564 
565     }
566 
567     private static class Appearance {
568         private Drawable mIcon;
569         private DrawableGetter mIconGetter;
570         private String mTitle;
571         private StringGetter mDescriptionGetter;
572         private BooleanGetter mChecked = FALSE_GETTER;
573 
toString()574         public String toString() {
575             StringBuilder stringBuilder = new StringBuilder()
576                 .append("'")
577                 .append(mTitle)
578                 .append("'");
579             if (mDescriptionGetter != null) {
580                 stringBuilder
581                     .append(" : '")
582                     .append(mDescriptionGetter.get())
583                     .append("'");
584             }
585             stringBuilder
586                 .append(" : '")
587                 .append(mChecked.get())
588                 .append("'");
589             return stringBuilder.toString();
590         }
591 
getTitle()592         public String getTitle() {
593             return mTitle;
594         }
595 
getIcon()596         public Drawable getIcon() {
597             if (mIconGetter != null) {
598                 return mIconGetter.get();
599             } else {
600                 return mIcon;
601             }
602         }
603 
getChecked()604         public BooleanGetter getChecked() {
605             return mChecked;
606         }
607 
setChecked(boolean checked)608         public void setChecked(boolean checked) {
609             mChecked = checked ? TRUE_GETTER : FALSE_GETTER;
610         }
611 
setChecked(BooleanGetter getter)612         public void setChecked(BooleanGetter getter) {
613             mChecked = getter;
614         }
615     }
616 
617     /**
618      * Header is a container for a sub-menu of "LayoutTreeNode" items.
619      */
620     public static class Header extends LayoutTreeBranch {
621         private final Appearance mAppearance = new Appearance();
622         private int mSelectedIndex = 0;
623         private String mDetailedDescription;
624         private int mContentIconRes = 0;
625         private boolean mEnabled = true;
626 
627         public static class Builder {
628             private final Resources mRes;
629             private final Header mHeader = new Header();
630 
Builder(Resources res)631             public Builder(Resources res) {
632                 mRes = res;
633             }
634 
icon(int resId)635             public Builder icon(int resId) {
636                 mHeader.mAppearance.mIcon = mRes.getDrawable(resId);
637                 return this;
638             }
639 
icon(DrawableGetter drawableGetter)640             public Builder icon(DrawableGetter drawableGetter) {
641                 mHeader.mAppearance.mIconGetter = drawableGetter;
642                 return this;
643             }
644 
contentIconRes(int resId)645             public Builder contentIconRes(int resId) {
646                 mHeader.mContentIconRes = resId;
647                 return this;
648             }
649 
title(int resId)650             public Builder title(int resId) {
651                 mHeader.mAppearance.mTitle = mRes.getString(resId);
652                 return this;
653             }
654 
description(int resId)655             public Builder description(int resId) {
656                 mHeader.mAppearance.mDescriptionGetter = new ResourceStringGetter(mRes, resId);
657                 return this;
658             }
659 
description(SelectionGroup selectionGroup)660             public Builder description(SelectionGroup selectionGroup) {
661                 mHeader.mAppearance.mDescriptionGetter = new SelectionGroupStringGetter(
662                         selectionGroup);
663                 return this;
664             }
665 
title(String title)666             public Builder title(String title) {
667                 mHeader.mAppearance.mTitle = title;
668                 return this;
669             }
670 
description(String description)671             public Builder description(String description) {
672                 mHeader.mAppearance.mDescriptionGetter = description != null ?
673                         new LiteralStringGetter(description) : null;
674                 return this;
675             }
676 
description(StringGetter description)677             public Builder description(StringGetter description) {
678                 mHeader.mAppearance.mDescriptionGetter = description;
679                 return this;
680             }
681 
detailedDescription(int resId)682             public Builder detailedDescription(int resId) {
683                 mHeader.mDetailedDescription = mRes.getString(resId);
684                 return this;
685             }
686 
detailedDescription(String detailedDescription)687             public Builder detailedDescription(String detailedDescription) {
688                 mHeader.mDetailedDescription = detailedDescription;
689                 return this;
690             }
691 
enabled(boolean enabled)692             public Builder enabled(boolean enabled) {
693                 mHeader.mEnabled = enabled;
694                 return this;
695             }
696 
build()697             public Header build() {
698                 return mHeader;
699             }
700         }
701 
702         @Override
getTitle()703         public String getTitle() {
704             return mAppearance.getTitle();
705         }
706 
add(LayoutTreeNode node)707         public Header add(LayoutTreeNode node) {
708             node.mParent = this;
709             mChildren.add(node);
710             return this;
711         }
712 
setSelectionGroup(SelectionGroup selectionGroup)713         public Header setSelectionGroup(SelectionGroup selectionGroup) {
714             selectionGroup.mParent = this;
715             mChildren.add(selectionGroup);
716             mAppearance.mDescriptionGetter = new SelectionGroupStringGetter(
717                     selectionGroup);
718             return this;
719         }
720 
getDetailedDescription()721         String getDetailedDescription() {
722             return mDetailedDescription;
723         }
724 
725         @Override
isEnabled()726         /* package */ boolean isEnabled() {
727             return mEnabled;
728         }
729 
730         @Override
getAppearance()731         /* package */ Appearance getAppearance() {
732             return mAppearance;
733         }
734 
735         @Override
Log(int level)736         /* package */ void Log(int level) {
737             Log.d("Layout", indent(level) + "Header  " + mAppearance);
738             for (LayoutTreeNode i : mChildren)
739                 i.Log(level + 1);
740         }
741     }
742 
743     public static class Action extends LayoutTreeNode {
744         public static final int ACTION_NONE = -1;
745         public static final int ACTION_INTENT = -2;
746         public static final int ACTION_BACK = -3;
747         private final int mActionId;
748         private final Intent mIntent;
749         private final Appearance mAppearance = new Appearance();
750         private Bundle mActionData;
751         private boolean mDefaultSelection = false;
752         private boolean mEnabled = true;
753 
Action(int id)754         public Action(int id) {
755             mActionId = id;
756             mIntent = null;
757         }
758 
Action(Intent intent)759         private Action(Intent intent) {
760             mActionId = ACTION_INTENT;
761             mIntent = intent;
762         }
763 
764         public static class Builder {
765             private final Resources mRes;
766             private final Action mAction;
767 
Builder(Resources res, int id)768             public Builder(Resources res, int id) {
769                 mRes = res;
770                 mAction = new Action(id);
771             }
772 
Builder(Resources res, Intent intent)773             public Builder(Resources res, Intent intent) {
774                 mRes = res;
775                 mAction = new Action(intent);
776             }
777 
title(int resId)778             public Builder title(int resId) {
779                 mAction.mAppearance.mTitle = mRes.getString(resId);
780                 return this;
781             }
782 
description(int resId)783             public Builder description(int resId) {
784                 mAction.mAppearance.mDescriptionGetter = new LiteralStringGetter(mRes.getString(
785                         resId));
786                 return this;
787             }
788 
title(String title)789             public Builder title(String title) {
790                 mAction.mAppearance.mTitle = title;
791                 return this;
792             }
793 
icon(int resId)794             public Builder icon(int resId) {
795                 mAction.mAppearance.mIcon = mRes.getDrawable(resId);
796                 return this;
797             }
798 
icon(Drawable drawable)799             public Builder icon(Drawable drawable) {
800                 mAction.mAppearance.mIcon = drawable;
801                 return this;
802             }
803 
description(String description)804             public Builder description(String description) {
805                 mAction.mAppearance.mDescriptionGetter = new LiteralStringGetter(description);
806                 return this;
807             }
808 
description(StringGetter description)809             public Builder description(StringGetter description) {
810                 mAction.mAppearance.mDescriptionGetter = description;
811                 return this;
812             }
813 
checked(boolean checked)814             public Builder checked(boolean checked) {
815                 mAction.mAppearance.setChecked(checked);
816                 return this;
817             }
818 
checked(BooleanGetter getter)819             public Builder checked(BooleanGetter getter) {
820                 mAction.mAppearance.setChecked(getter);
821                 return this;
822             }
823 
data(Bundle data)824             public Builder data(Bundle data) {
825                 mAction.mActionData = data;
826                 return this;
827             }
828 
829             /*
830              * Makes this action default initial selection when the list is displayed.
831              */
defaultSelection()832             public Builder defaultSelection() {
833                 mAction.mDefaultSelection = true;
834                 return this;
835             }
836 
enabled(boolean enabled)837             public Builder enabled(boolean enabled) {
838                 mAction.mEnabled = enabled;
839                 return this;
840             }
841 
build()842             public Action build() {
843                 return mAction;
844             }
845         }
846 
847         @Override
isEnabled()848         /* package */ boolean isEnabled() {
849             return mEnabled;
850         }
851 
852         @Override
getAppearance()853         /* package */ Appearance getAppearance() {
854             return mAppearance;
855         }
856 
857         @Override
Log(int level)858         /* package */ void Log(int level) {
859             Log.d("Layout", indent(level) + "Action  #" + mActionId + "  " + mAppearance);
860         }
861 
getId()862         public int getId() {
863             return mActionId;
864         }
865 
getIntent()866         public Intent getIntent() {
867             return mIntent;
868         }
869 
870         @Override
getTitle()871         public String getTitle() {
872             return mAppearance.getTitle();
873         }
874 
getData()875         public Bundle getData() {
876             return mActionData;
877         }
878     }
879 
880     public static class Status extends LayoutTreeNode {
881         private final Appearance mAppearance = new Appearance();
882         private boolean mEnabled = true;
883 
884         public static class Builder {
885             private final Resources mRes;
886             private final Status mStatus = new Status();
887 
Builder(Resources res)888             public Builder(Resources res) {
889                 mRes = res;
890             }
891 
icon(int resId)892             public Builder icon(int resId) {
893                 mStatus.mAppearance.mIcon = mRes.getDrawable(resId);
894                 return this;
895             }
896 
title(int resId)897             public Builder title(int resId) {
898                 mStatus.mAppearance.mTitle = mRes.getString(resId);
899                 return this;
900             }
901 
description(int resId)902             public Builder description(int resId) {
903                 mStatus.mAppearance.mDescriptionGetter = new LiteralStringGetter(mRes.getString(
904                         resId));
905                 return this;
906             }
907 
title(String title)908             public Builder title(String title) {
909                 mStatus.mAppearance.mTitle = title;
910                 return this;
911             }
912 
description(String description)913             public Builder description(String description) {
914                 mStatus.mAppearance.mDescriptionGetter = new LiteralStringGetter(description);
915                 return this;
916             }
917 
description(StringGetter description)918             public Builder description(StringGetter description) {
919                 mStatus.mAppearance.mDescriptionGetter = description;
920                 return this;
921             }
922 
enabled(boolean enabled)923             public Builder enabled(boolean enabled) {
924                 mStatus.mEnabled = enabled;
925                 return this;
926             }
927 
build()928             public Status build() {
929                 return mStatus;
930             }
931         }
932 
933         @Override
getTitle()934         public String getTitle() {
935             return mAppearance.getTitle();
936         }
937 
938         @Override
isEnabled()939         /* package */ boolean isEnabled() {
940             return mEnabled;
941         }
942 
943         @Override
getAppearance()944         /* package */ Appearance getAppearance() {
945             return mAppearance;
946         }
947 
948         @Override
Log(int level)949         /* package */ void Log(int level) {
950             Log.d("Layout", indent(level) + "Status  " + mAppearance);
951         }
952     }
953 
954     public static class Static extends LayoutTreeNode {
955         private String mTitle;
956 
957         public static class Builder {
958             private final Resources mRes;
959             private final Static mStatic = new Static();
960 
Builder(Resources res)961             public Builder(Resources res) {
962                 mRes = res;
963             }
964 
title(int resId)965             public Builder title(int resId) {
966                 mStatic.mTitle = mRes.getString(resId);
967                 return this;
968             }
969 
title(String title)970             public Builder title(String title) {
971                 mStatic.mTitle = title;
972                 return this;
973             }
974 
build()975             public Static build() {
976                 return mStatic;
977             }
978         }
979 
980         @Override
getTitle()981         public String getTitle() {
982             return mTitle;
983         }
984 
985         @Override
getAppearance()986         /* package */ Appearance getAppearance() {
987             return null;
988         }
989 
990         @Override
Log(int level)991         /* package */ void Log(int level) {
992             Log.d("Layout", indent(level) + "Static  '" + mTitle + "'");
993         }
994     }
995 
996     public static class WallOfText extends LayoutTreeNode {
997         private String mTitle;
998 
999         public static class Builder {
1000             private final Resources mRes;
1001             private final WallOfText mWallOfText = new WallOfText();
1002 
Builder(Resources res)1003             public Builder(Resources res) {
1004                 mRes = res;
1005             }
1006 
title(int resId)1007             public Builder title(int resId) {
1008                 mWallOfText.mTitle = mRes.getString(resId);
1009                 return this;
1010             }
1011 
title(String title)1012             public Builder title(String title) {
1013                 mWallOfText.mTitle = title;
1014                 return this;
1015             }
1016 
build()1017             public WallOfText build() {
1018                 return mWallOfText;
1019             }
1020         }
1021 
1022         @Override
getTitle()1023         public String getTitle() {
1024             return mTitle;
1025         }
1026 
1027         @Override
getAppearance()1028         /* package */ Appearance getAppearance() {
1029             return null;
1030         }
1031 
1032         @Override
Log(int level)1033         /* package */ void Log(int level) {
1034             Log.d("Layout", indent(level) + "Static  '" + mTitle + "'");
1035         }
1036     }
1037 
1038     /**
1039      * Pointer to currently visible item.
1040      */
1041     private Header mNavigationCursor;
1042 
1043     /**
1044      * Index of selected item when items are displayed. This is used by LayoutGetter to implemented
1045      * selection stability, where a LayoutGetter can arrange for a list that is refreshed regularly
1046      * to carry forward a selection.
1047      */
1048     private int mInitialItemIndex = -1;
1049     private final ArrayList<LayoutRow> mLayoutRows = new ArrayList<>();
1050     private final ArrayList<LayoutGetter> mVisibleLayoutGetters = new ArrayList<>();
1051     private final ArrayList<LayoutTreeNode> mChildren = new ArrayList<>();
1052     private String mTopLevelBreadcrumb = "";
1053     private LayoutNodeRefreshListener mListener;
1054 
getLayoutRows()1055     public ArrayList<LayoutRow> getLayoutRows() {
1056         return mLayoutRows;
1057     }
1058 
setRefreshViewListener(LayoutNodeRefreshListener listener)1059     public void setRefreshViewListener(LayoutNodeRefreshListener listener) {
1060         mListener = listener;
1061         for (final LayoutGetter getter : mVisibleLayoutGetters) {
1062             getter.setListener(listener);
1063         }
1064     }
1065 
1066     /**
1067      * Return the breadcrumb the user should see in the content pane.
1068      */
getBreadcrumb()1069     public String getBreadcrumb() {
1070       if (mNavigationCursor.mParent == null) {
1071           // At the top level of the layout.
1072           return mTopLevelBreadcrumb;
1073       } else {
1074           // Showing a header down the hierarchy, breadcrumb is title of item above.
1075           return ((Header) (mNavigationCursor.mParent)).mAppearance.mTitle;
1076       }
1077     }
1078 
1079     /**
1080      * Navigate up one level, return true if a parent node is now visible. Return false if the
1081      * already at the top level node. The controlling fragment interprets a false return value as
1082      * "stop activity".
1083      */
goBack()1084     public boolean goBack() {
1085         if (mNavigationCursor.mParent != null) {
1086             mNavigationCursor = (Header) mNavigationCursor.mParent;
1087             updateLayoutRows();
1088             return true;
1089         }
1090         return false;
1091     }
1092 
1093     /**
1094      * Parcelable implementation.
1095      */
Layout(Parcel in)1096     public Layout(Parcel in) {
1097     }
1098 
Layout()1099     public Layout() {
1100         mNavigationCursor = null;
1101     }
1102 
1103     @Override
describeContents()1104     public int describeContents() {
1105         return 0;
1106     }
1107 
1108     @Override
writeToParcel(Parcel out, int flags)1109     public void writeToParcel(Parcel out, int flags) {
1110     }
1111 
1112     public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
1113         public Layout createFromParcel(Parcel in) {
1114             return new Layout(in);
1115         }
1116 
1117         public Layout[] newArray(int size) {
1118             return new Layout[size];
1119         }
1120     };
1121 
getTitle()1122     String getTitle() {
1123         return mNavigationCursor.mAppearance.mTitle;
1124     }
1125 
getIcon()1126     Drawable getIcon() {
1127         return mNavigationCursor.mAppearance.getIcon();
1128     }
1129 
getDescription()1130     String getDescription() {
1131         return mNavigationCursor.getDetailedDescription();
1132     }
1133 
goToTitle(String title)1134     public void goToTitle(String title) {
1135         while (mNavigationCursor.mParent != null) {
1136             mNavigationCursor = (Header) (mNavigationCursor.mParent);
1137             if (TextUtils.equals(mNavigationCursor.mAppearance.mTitle, title)) {
1138                 break;
1139             }
1140         }
1141         updateLayoutRows();
1142     }
1143 
1144     /*
1145      * Respond to a user click on "layoutRow" and return "true" if the state of the display has
1146      * changed. A controlling fragment will respond to a "true" return by updating the view.
1147      */
onClickNavigate(LayoutRow layoutRow)1148     public boolean onClickNavigate(LayoutRow layoutRow) {
1149         LayoutTreeNode node = layoutRow.mNode;
1150         if (node instanceof Header) {
1151             mNavigationCursor.mSelectedIndex = mLayoutRows.indexOf(layoutRow);
1152             mNavigationCursor = (Header) node;
1153             updateLayoutRows();
1154             return true;
1155         }
1156         return false;
1157     }
1158 
reloadLayoutRows()1159     public void reloadLayoutRows() {
1160         updateLayoutRows();
1161     }
1162 
add(Header header)1163     public Layout add(Header header) {
1164         header.mParent = null;
1165         mChildren.add(header);
1166         return this;
1167     }
1168 
add(LayoutTreeNode leaf)1169     public Layout add(LayoutTreeNode leaf) {
1170         leaf.mParent = null;
1171         mChildren.add(leaf);
1172         return this;
1173     }
1174 
breadcrumb(String topLevelBreadcrumb)1175     public Layout breadcrumb(String topLevelBreadcrumb) {
1176         mTopLevelBreadcrumb = topLevelBreadcrumb;
1177         return this;
1178     }
1179 
1180     /**
1181      * Sets the selected node to the first top level node with its title member equal to "title". If
1182      * "title" is null, empty, or there are no top level nodes with a title member equal to "title",
1183      * set the first node in the list as the selected.
1184      */
setSelectedByTitle(String title)1185     public Layout setSelectedByTitle(String title) {
1186         for (int i = 0; i < mChildren.size(); ++i) {
1187             if (TextUtils.equals(mChildren.get(i).getTitle(), title)) {
1188                 mInitialItemIndex = i;
1189                 break;
1190             }
1191         }
1192         return this;
1193     }
1194 
Log(int level)1195     public void Log(int level) {
1196         for (LayoutTreeNode i : mChildren) {
1197             i.Log(level + 1);
1198         }
1199     }
1200 
Log()1201     public void Log() {
1202         Log.d("Layout", "----- Layout");
1203         Log(0);
1204     }
1205 
navigateToRoot()1206     public void navigateToRoot() {
1207         if (mChildren.size() > 0) {
1208             mNavigationCursor = (Header) mChildren.get(0);
1209         } else {
1210             mNavigationCursor = null;
1211         }
1212         updateLayoutRows();
1213     }
1214 
getSelectedIndex()1215     public int getSelectedIndex() {
1216         return mNavigationCursor.mSelectedIndex;
1217     }
1218 
setSelectedIndex(int index)1219     public void setSelectedIndex(int index) {
1220         mNavigationCursor.mSelectedIndex = index;
1221     }
1222 
setParentSelectedIndex(int index)1223     public void setParentSelectedIndex(int index) {
1224         if (mNavigationCursor.mParent != null) {
1225             Header u = (Header) mNavigationCursor.mParent;
1226             u.mSelectedIndex = index;
1227         }
1228     }
1229 
addNodeListToLayoutRows(ArrayList<LayoutTreeNode> list)1230     private void addNodeListToLayoutRows(ArrayList<LayoutTreeNode> list) {
1231         for (LayoutTreeNode node : list) {
1232             if (node instanceof LayoutGetter) {
1233                 // Add subitems of "node" recursively.
1234                 LayoutGetter layoutGetter = (LayoutGetter) node;
1235                 layoutGetter.setListener(mListener);
1236                 mVisibleLayoutGetters.add(layoutGetter);
1237                 Layout layout = layoutGetter.get();
1238                 for (LayoutTreeNode child : layout.mChildren) {
1239                     child.mParent = mNavigationCursor;
1240                 }
1241                 int initialIndex = layout.mInitialItemIndex;
1242                 if (initialIndex != -1) {
1243                     mNavigationCursor.mSelectedIndex = mLayoutRows.size() + initialIndex;
1244                 }
1245                 addNodeListToLayoutRows(layout.mChildren);
1246             } else if (node instanceof SelectionGroup) {
1247                 SelectionGroup sg = (SelectionGroup) node;
1248                 for (int i = 0; i < sg.size(); ++i) {
1249                     mLayoutRows.add(new LayoutRow(sg, i));
1250                 }
1251             } else {
1252                 if (node instanceof Action && ((Action) node).mDefaultSelection) {
1253                     mNavigationCursor.mSelectedIndex = mLayoutRows.size();
1254                 }
1255                 mLayoutRows.add(new LayoutRow(node));
1256             }
1257         }
1258     }
1259 
updateLayoutRows()1260     private void updateLayoutRows() {
1261         mLayoutRows.clear();
1262         for (LayoutGetter layoutGetter : mVisibleLayoutGetters) {
1263             layoutGetter.notVisible();
1264         }
1265         mVisibleLayoutGetters.clear();
1266         addNodeListToLayoutRows(mNavigationCursor.mChildren);
1267 
1268         // Skip past any unselectable items
1269         final int rowCount = mLayoutRows.size();
1270         while (mNavigationCursor.mSelectedIndex < rowCount - 1 &&
1271                 mNavigationCursor.mSelectedIndex >= 0 &&
1272                 ((mLayoutRows.get(mNavigationCursor.mSelectedIndex).mViewType
1273                         == LayoutRow.VIEW_TYPE_STATIC) ||
1274                 (mLayoutRows.get(mNavigationCursor.mSelectedIndex).mViewType
1275                         == LayoutRow.VIEW_TYPE_WALLOFTEXT))) {
1276             mNavigationCursor.mSelectedIndex++;
1277         }
1278     }
1279 
indent(int level)1280     private static String indent(int level) {
1281         StringBuilder s = new StringBuilder();
1282         for (int i = 0; i < level; ++i) {
1283             s.append("  ");
1284         }
1285         return s.toString();
1286     }
1287 }
1288