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.Context;
20 import android.content.Intent;
21 import android.content.res.Resources;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
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 
Log(int level)56         void Log(int level) {
57         }
58     }
59 
60     private abstract static class LayoutTreeBranch extends LayoutTreeNode {
61         ArrayList<LayoutTreeNode> mChildren;
LayoutTreeBranch()62         LayoutTreeBranch() {
63             mChildren = new ArrayList<LayoutTreeNode>();
64         }
65     }
66 
67     public static class LayoutRow {
68         public static final int NO_CHECK_SET = 0;
69         public static final int VIEW_TYPE_ACTION = 0;
70         public static final int VIEW_TYPE_STATIC = 1;
71 
72         private String mTitle;
73         private StringGetter mDescription;
74         private LayoutTreeNode mNode;
75         private boolean mEnabled;
76         private int mViewType;
77         private boolean mChecked = false;
78         private Drawable mIcon = null;
79 
getNode()80         public Node getNode() {
81             return mNode;
82         }
83 
getIconUri()84         public Uri getIconUri() {
85             return null;
86         }
87 
getIcon()88         public Drawable getIcon() {
89             return mIcon;
90         }
91 
getCheckSetId()92         public int getCheckSetId() {
93             return 0;
94         }
95 
isChecked()96         public boolean isChecked() {
97             return mChecked;
98         }
99 
setChecked(boolean v)100         public void setChecked(boolean v) {
101             mChecked = v;
102         }
103 
infoOnly()104         public boolean infoOnly() {
105             return false;
106         }
107 
isEnabled()108         public boolean isEnabled() {
109             return mEnabled;
110         }
111 
hasNext()112         public boolean hasNext() {
113             return false;
114         }
115 
hasMultilineDescription()116         public boolean hasMultilineDescription() {
117             return false;
118         }
119 
getTitle()120         public String getTitle() {
121             return mTitle;
122         }
123 
getDescription()124         public StringGetter getDescription() {
125             return mDescription;
126         }
127 
getViewType()128         public int getViewType() {
129             return mViewType;
130         }
131 
isGoBack()132         public boolean isGoBack() {
133             if (mNode instanceof Action) {
134                 Action a = (Action) mNode;
135                 if (a.mActionId == Action.ACTION_BACK) {
136                     return true;
137                 }
138             }
139             return false;
140         }
141 
getUserAction()142         public Action getUserAction() {
143             if (mNode instanceof Action) {
144                 Action a = (Action) mNode;
145                 if (a.mActionId != Action.ACTION_NONE) {
146                     return a;
147                 }
148             }
149             return null;
150         }
151 
getContentIconRes()152         public int getContentIconRes() {
153             if (mNode instanceof Header) {
154                 return ((Header) mNode).mContentIconRes;
155             }
156             return 0;
157         }
158 
LayoutRow(LayoutTreeNode node)159         public LayoutRow(LayoutTreeNode node) {
160             mNode = node;
161             mViewType = VIEW_TYPE_ACTION;
162             Appearence a;
163             if (node instanceof Header) {
164                 a = ((Header) node).mAppearence;
165                 mEnabled = true;
166             } else if (node instanceof Action) {
167                 a = ((Action) node).mAppearence;
168                 mEnabled = true;
169             } else if (node instanceof Status) {
170                 a = ((Status) node).mAppearence;
171                 mEnabled = true;
172             } else {
173                 a = null;
174                 mEnabled = false;
175                 if (node instanceof Static) {
176                     mViewType = VIEW_TYPE_STATIC;
177                     Static s = (Static) node;
178                     mTitle = s.mTitle;
179                 }
180             }
181             if (a != null) {
182                 mTitle = a.getTitle();
183                 mDescription = a.mDescriptionGetter;
184                 mIcon = a.getIcon();
185                 mChecked = a.isChecked();
186             }
187         }
188     }
189 
190     public abstract static class DrawableGetter {
get()191         public abstract Drawable get();
192 
193         /**
194          * Notification from client that antecedent data has changed and the drawable should be
195          * redisplayed.
196          */
refreshView()197         public void refreshView() {
198             //TODO - When implementing, ensure that multiple updates from the same event do not
199             // cause multiple view updates.
200         }
201     }
202 
203     public abstract static class StringGetter {
204         private ContentNodeRefreshListener mListener;
205 
setListener(ContentNodeRefreshListener listener)206         public void setListener(ContentNodeRefreshListener listener) {
207             mListener = listener;
208         }
209 
get()210         public abstract String get();
211 
212         /**
213          * Notification from client that antecedent data has changed and the string should be
214          * redisplayed.
215          */
refreshView()216         public void refreshView() {
217             if (mListener != null) {
218                 mListener.onRefreshView();
219             }
220         }
221     }
222 
223     /**
224      * Implementation of "StringGetter" that stores and returns a literal string.
225      */
226     private static class LiteralStringGetter extends StringGetter {
227         private final String mValue;
get()228         public String get() {
229             return mValue;
230         }
LiteralStringGetter(String value)231         LiteralStringGetter(String value) {
232             mValue = value;
233         }
234     }
235 
236     /**
237      * Implementation of "StringGetter" that stores a string resource id and returns a string.
238      */
239     private static class ResourceStringGetter extends StringGetter {
240         private final int mStringResourceId;
241         private final Resources mRes;
get()242         public String get() {
243             return mRes.getString(mStringResourceId);
244         }
ResourceStringGetter(Resources res, int stringResourceId)245         ResourceStringGetter(Resources res, int stringResourceId) {
246             mRes = res;
247             mStringResourceId = stringResourceId;
248         }
249     }
250 
251     public abstract static class LayoutGetter extends LayoutTreeNode {
252         // Layout manages this listener; removing it when this node is not visible and setting it
253         // when it is.  Users are expected to set the listener with Layout.setRefreshViewListener.
254         private LayoutNodeRefreshListener mListener;
255 
setListener(LayoutNodeRefreshListener listener)256         public void setListener(LayoutNodeRefreshListener listener) {
257             mListener = listener;
258         }
259 
notVisible()260         public void notVisible() {
261             mListener = null;
262         }
263 
get()264         public abstract Layout get();
265 
getSelectedNode()266         public Node getSelectedNode() {
267             if (mListener != null) {
268                 return mListener.getSelectedNode();
269             } else {
270                 return null;
271             }
272         }
273 
274         /**
275          * Notification from client that antecedent data has changed and the list containing the
276          * contents of this getter should be updated.
277          */
refreshView()278         public void refreshView() {
279             if (mListener != null) {
280                 mListener.onRefreshView();
281             }
282         }
283 
284         @Override
getTitle()285         public String getTitle() {
286             return null;
287         }
288 
Log(int level)289         void Log(int level) {
290             Log.d("Layout", indent(level) + "LayoutGetter");
291             Layout l = get();
292             l.Log(level + 1);
293         }
294     }
295 
296     private static class Appearence {
297         private Drawable mIcon;
298         private DrawableGetter mIconGetter;
299         private String mTitle;
300         private StringGetter mDescriptionGetter;
301         private boolean mChecked = false;
302 
toString()303         public String toString() {
304             StringBuilder stringBuilder = new StringBuilder()
305                 .append("'")
306                 .append(mTitle)
307                 .append("'");
308             if (mDescriptionGetter != null) {
309                 stringBuilder
310                     .append(" : '")
311                     .append(mDescriptionGetter.get())
312                     .append("'");
313             }
314             stringBuilder
315                 .append(" : '")
316                 .append(mChecked)
317                 .append("'");
318             return stringBuilder.toString();
319         }
320 
getTitle()321         public String getTitle() {
322             return mTitle;
323         }
324 
getIcon()325         public Drawable getIcon() {
326             if (mIconGetter != null) {
327                 return mIconGetter.get();
328             } else {
329                 return mIcon;
330             }
331         }
332 
isChecked()333         public boolean isChecked() {
334             return mChecked;
335         }
336     }
337 
338     /**
339      * Header is a container for a sub-menu of "LayoutTreeNode" items.
340      */
341     public static class Header extends LayoutTreeBranch {
342         private Appearence mAppearence = new Appearence();
343         private int mSelectedIndex = 0;
344         private String mDetailedDescription;
345         private int mContentIconRes = 0;
346 
347         public static class Builder {
348             private Resources mRes;
349             private Header mHeader = new Header();
350 
Builder(Resources res)351             public Builder(Resources res) {
352                 mRes = res;
353             }
354 
icon(int resId)355             public Builder icon(int resId) {
356                 mHeader.mAppearence.mIcon = mRes.getDrawable(resId);
357                 return this;
358             }
359 
icon(DrawableGetter drawableGetter)360             public Builder icon(DrawableGetter drawableGetter) {
361                 mHeader.mAppearence.mIconGetter = drawableGetter;
362                 return this;
363             }
364 
contentIconRes(int resId)365             public Builder contentIconRes(int resId) {
366                 mHeader.mContentIconRes = resId;
367                 return this;
368             }
369 
title(int resId)370             public Builder title(int resId) {
371                 mHeader.mAppearence.mTitle = mRes.getString(resId);
372                 return this;
373             }
374 
description(int resId)375             public Builder description(int resId) {
376                 mHeader.mAppearence.mDescriptionGetter = new ResourceStringGetter(mRes, resId);
377                 return this;
378             }
379 
title(String title)380             public Builder title(String title) {
381                 mHeader.mAppearence.mTitle = title;
382                 return this;
383             }
384 
description(String description)385             public Builder description(String description) {
386                 mHeader.mAppearence.mDescriptionGetter = new LiteralStringGetter(description);
387                 return this;
388             }
389 
description(StringGetter description)390             public Builder description(StringGetter description) {
391                 mHeader.mAppearence.mDescriptionGetter = description;
392                 return this;
393             }
394 
detailedDescription(int resId)395             public Builder detailedDescription(int resId) {
396                 mHeader.mDetailedDescription = mRes.getString(resId);
397                 return this;
398             }
399 
detailedDescription(String detailedDescription)400             public Builder detailedDescription(String detailedDescription) {
401                 mHeader.mDetailedDescription = detailedDescription;
402                 return this;
403             }
404 
build()405             public Header build() {
406                 return mHeader;
407             }
408         }
409 
410         @Override
getTitle()411         public String getTitle() {
412             return mAppearence.getTitle();
413         }
414 
add(LayoutTreeNode node)415         public Header add(LayoutTreeNode node) {
416             node.mParent = this;
417             mChildren.add(node);
418             return this;
419         }
420 
getDetailedDescription()421         String getDetailedDescription() {
422             return mDetailedDescription;
423         }
424 
Log(int level)425         void Log(int level) {
426             Log.d("Layout", indent(level) + "Header  " + mAppearence);
427             for (LayoutTreeNode i : mChildren)
428                 i.Log(level + 1);
429         }
430     }
431 
432     public static class Action extends LayoutTreeNode {
433         public static final int ACTION_NONE = -1;
434         public static final int ACTION_INTENT = -2;
435         public static final int ACTION_BACK = -3;
436         private int mActionId;
437         private Intent mIntent;
438         private Appearence mAppearence = new Appearence();
439         private Bundle mActionData;
440         private boolean mDefaultSelection = false;
441 
Action(int id)442         private Action(int id) {
443             mActionId = id;
444         }
445 
Action(Intent intent)446         private Action(Intent intent) {
447             mActionId = ACTION_INTENT;
448             mIntent = intent;
449         }
450 
451         public static class Builder {
452             private Resources mRes;
453             private Action mAction;
454 
Builder(Resources res, int id)455             public Builder(Resources res, int id) {
456                 mRes = res;
457                 mAction = new Action(id);
458             }
459 
Builder(Resources res, Intent intent)460             public Builder(Resources res, Intent intent) {
461                 mRes = res;
462                 mAction = new Action(intent);
463             }
464 
title(int resId)465             public Builder title(int resId) {
466                 mAction.mAppearence.mTitle = mRes.getString(resId);
467                 return this;
468             }
469 
description(int resId)470             public Builder description(int resId) {
471                 mAction.mAppearence.mDescriptionGetter = new LiteralStringGetter(mRes.getString(
472                         resId));
473                 return this;
474             }
475 
title(String title)476             public Builder title(String title) {
477                 mAction.mAppearence.mTitle = title;
478                 return this;
479             }
480 
icon(int resId)481              public Builder icon(int resId) {
482                  mAction.mAppearence.mIcon = mRes.getDrawable(resId);
483                  return this;
484              }
485 
description(String description)486             public Builder description(String description) {
487                 mAction.mAppearence.mDescriptionGetter = new LiteralStringGetter(description);
488                 return this;
489             }
490 
description(StringGetter description)491             public Builder description(StringGetter description) {
492                 mAction.mAppearence.mDescriptionGetter = description;
493                 return this;
494             }
495 
checked(boolean checked)496             public Builder checked(boolean checked) {
497                 mAction.mAppearence.mChecked = checked;
498                 return this;
499             }
500 
data(Bundle data)501             public Builder data(Bundle data) {
502                 mAction.mActionData = data;
503                 return this;
504             }
505 
506             /*
507              * Makes this action default initial selection when the list is displayed.
508              */
defaultSelection()509             public Builder defaultSelection() {
510                 mAction.mDefaultSelection = true;
511                 return this;
512             }
513 
build()514             public Action build() {
515                 return mAction;
516             }
517         }
518 
Log(int level)519         void Log(int level) {
520             Log.d("Layout", indent(level) + "Action  #" + mActionId + "  " + mAppearence);
521         }
522 
getId()523         public int getId() {
524             return mActionId;
525         }
526 
getIntent()527         public Intent getIntent() {
528             return mIntent;
529         }
530 
531         @Override
getTitle()532         public String getTitle() {
533             return mAppearence.getTitle();
534         }
535 
getData()536         public Bundle getData() {
537             return mActionData;
538         }
539     }
540 
541     public static class Status extends LayoutTreeNode {
542         private Appearence mAppearence = new Appearence();
543 
544         public static class Builder {
545             private Resources mRes;
546             private Status mStatus = new Status();
547 
Builder(Resources res)548             public Builder(Resources res) {
549                 mRes = res;
550             }
551 
icon(int resId)552             public Builder icon(int resId) {
553                 mStatus.mAppearence.mIcon = mRes.getDrawable(resId);
554                 return this;
555             }
556 
title(int resId)557             public Builder title(int resId) {
558                 mStatus.mAppearence.mTitle = mRes.getString(resId);
559                 return this;
560             }
561 
description(int resId)562             public Builder description(int resId) {
563                 mStatus.mAppearence.mDescriptionGetter = new LiteralStringGetter(mRes.getString(
564                         resId));
565                 return this;
566             }
567 
title(String title)568             public Builder title(String title) {
569                 mStatus.mAppearence.mTitle = title;
570                 return this;
571             }
572 
description(String description)573             public Builder description(String description) {
574                 mStatus.mAppearence.mDescriptionGetter = new LiteralStringGetter(description);
575                 return this;
576             }
577 
description(StringGetter description)578             public Builder description(StringGetter description) {
579                 mStatus.mAppearence.mDescriptionGetter = description;
580                 return this;
581             }
582 
build()583             public Status build() {
584                 return mStatus;
585             }
586         }
587 
588         @Override
getTitle()589         public String getTitle() {
590             return mAppearence.getTitle();
591         }
592 
Log(int level)593         void Log(int level) {
594             Log.d("Layout", indent(level) + "Status  " + mAppearence);
595         }
596     }
597 
598     public static class Static extends LayoutTreeNode {
599         private String mTitle;
600 
601         public static class Builder {
602             private Resources mRes;
603             private Static mStatic = new Static();
604 
Builder(Resources res)605             public Builder(Resources res) {
606                 mRes = res;
607             }
608 
title(int resId)609             public Builder title(int resId) {
610                 mStatic.mTitle = mRes.getString(resId);
611                 return this;
612             }
613 
title(String title)614             public Builder title(String title) {
615                 mStatic.mTitle = title;
616                 return this;
617             }
618 
build()619             public Static build() {
620                 return mStatic;
621             }
622         }
623 
624         @Override
getTitle()625         public String getTitle() {
626             return mTitle;
627         }
628 
Log(int level)629         void Log(int level) {
630             Log.d("Layout", indent(level) + "Static  '" + mTitle + "'");
631         }
632     }
633 
634     /**
635      * Pointer to currently visible item.
636      */
637     private Header mNavigationCursor;
638 
639     /**
640      * Index of selected item when items are displayed. This is used by LayoutGetter to implemented
641      * selection stability, where a LayoutGetter can arrange for a list that is refreshed regularly
642      * to carry forward a selection.
643      */
644     private int mInitialItemIndex = -1;
645     private final ArrayList<LayoutRow> mLayoutRows = new ArrayList<LayoutRow>();
646     private final ArrayList<LayoutGetter> mVisibleLayoutGetters = new ArrayList<LayoutGetter>();
647     private final ArrayList<LayoutTreeNode> mChildren = new ArrayList<LayoutTreeNode>();
648     private String mTopLevelBreadcrumb = "";
649     private LayoutNodeRefreshListener mListener;
650 
getLayoutRows()651     public ArrayList<LayoutRow> getLayoutRows() {
652         return mLayoutRows;
653     }
654 
setRefreshViewListener(LayoutNodeRefreshListener listener)655     public void setRefreshViewListener(LayoutNodeRefreshListener listener) {
656         mListener = listener;
657     }
658 
659     /**
660      * Return the breadcrumb the user should see in the content pane.
661      */
getBreadcrumb()662     public String getBreadcrumb() {
663       if (mNavigationCursor.mParent == null) {
664           // At the top level of the layout.
665           return mTopLevelBreadcrumb;
666       } else {
667           // Showing a header down the hierarchy, breadcrumb is title of item above.
668           return ((Header) (mNavigationCursor.mParent)).mAppearence.mTitle;
669       }
670     }
671 
672     /**
673      * Navigate up one level, return true if a parent node is now visible. Return false if the
674      * already at the top level node. The controlling fragment interprets a false return value as
675      * "stop activity".
676      */
goBack()677     public boolean goBack() {
678         if (mNavigationCursor.mParent != null) {
679             Header u = (Header) mNavigationCursor.mParent;
680             if (u != null) {
681                 mNavigationCursor = u;
682                 updateLayoutRows();
683                 return true;
684             }
685         }
686         return false;
687     }
688 
689     /**
690      * Parcelable implementation.
691      */
Layout(Parcel in)692     public Layout(Parcel in) {
693     }
694 
Layout()695     public Layout() {
696         mNavigationCursor = null;
697     }
698 
699     @Override
describeContents()700     public int describeContents() {
701         return 0;
702     }
703 
704     @Override
writeToParcel(Parcel out, int flags)705     public void writeToParcel(Parcel out, int flags) {
706     }
707 
708     public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
709         public Layout createFromParcel(Parcel in) {
710             return new Layout(in);
711         }
712 
713         public Layout[] newArray(int size) {
714             return new Layout[size];
715         }
716     };
717 
getTitle()718     String getTitle() {
719         return mNavigationCursor.mAppearence.mTitle;
720     }
721 
getIcon()722     Drawable getIcon() {
723         return mNavigationCursor.mAppearence.getIcon();
724     }
725 
getDescription()726     String getDescription() {
727         return mNavigationCursor.getDetailedDescription();
728     }
729 
goToTitle(String title)730     public void goToTitle(String title) {
731         while (mNavigationCursor.mParent != null) {
732             mNavigationCursor = (Header) (mNavigationCursor.mParent);
733             if (TextUtils.equals(mNavigationCursor.mAppearence.mTitle, title)) {
734                 break;
735             }
736         }
737         updateLayoutRows();
738     }
739 
740     /*
741      * Respond to a user click on "layoutRow" and return "true" if the state of the display has
742      * changed. A controlling fragment will respond to a "true" return by updating the view.
743      */
onClickNavigate(LayoutRow layoutRow)744     public boolean onClickNavigate(LayoutRow layoutRow) {
745         LayoutTreeNode node = layoutRow.mNode;
746         if (node instanceof Header) {
747             mNavigationCursor.mSelectedIndex = mLayoutRows.indexOf(layoutRow);
748             mNavigationCursor = (Header) node;
749             updateLayoutRows();
750             return true;
751         }
752         return false;
753     }
754 
reloadLayoutRows()755     public void reloadLayoutRows() {
756         updateLayoutRows();
757     }
758 
add(Header header)759     public Layout add(Header header) {
760         header.mParent = null;
761         mChildren.add(header);
762         return this;
763     }
764 
add(LayoutTreeNode leaf)765     public Layout add(LayoutTreeNode leaf) {
766         leaf.mParent = null;
767         mChildren.add(leaf);
768         return this;
769     }
770 
breadcrumb(String topLevelBreadcrumb)771     public Layout breadcrumb(String topLevelBreadcrumb) {
772         mTopLevelBreadcrumb = topLevelBreadcrumb;
773         return this;
774     }
775 
776     /**
777      * Sets the selected node to the first top level node with its title member equal to "title". If
778      * "title" is null, empty, or there are no top level nodes with a title member equal to "title",
779      * set the first node in the list as the selected.
780      */
setSelectedByTitle(String title)781     public Layout setSelectedByTitle(String title) {
782         for (int i = 0; i < mChildren.size(); ++i) {
783             if (TextUtils.equals(mChildren.get(i).getTitle(), title)) {
784                 mInitialItemIndex = i;
785                 break;
786             }
787         }
788         return this;
789     }
790 
Log(int level)791     public void Log(int level) {
792         for (LayoutTreeNode i : mChildren) {
793             i.Log(level + 1);
794         }
795     }
796 
Log()797     public void Log() {
798         Log.d("Layout", "----- Layout");
799         Log(0);
800     }
801 
navigateToRoot()802     public void navigateToRoot() {
803         if (mChildren.size() > 0) {
804             mNavigationCursor = (Header) mChildren.get(0);
805         } else {
806             mNavigationCursor = null;
807         }
808         updateLayoutRows();
809     }
810 
getSelectedIndex()811     public int getSelectedIndex() {
812         return mNavigationCursor.mSelectedIndex;
813     }
814 
setSelectedIndex(int index)815     public void setSelectedIndex(int index) {
816         mNavigationCursor.mSelectedIndex = index;
817     }
818 
setParentSelectedIndex(int index)819     public void setParentSelectedIndex(int index) {
820         if (mNavigationCursor.mParent != null) {
821             Header u = (Header) mNavigationCursor.mParent;
822             u.mSelectedIndex = index;
823         }
824     }
825 
addNodeListToLayoutRows(ArrayList<LayoutTreeNode> list)826     private void addNodeListToLayoutRows(ArrayList<LayoutTreeNode> list) {
827         for (LayoutTreeNode node : list) {
828             if (node instanceof LayoutGetter) {
829                 // Add subitems of "node" recursively.
830                 LayoutGetter layoutGetter = (LayoutGetter) node;
831                 layoutGetter.setListener(mListener);
832                 mVisibleLayoutGetters.add(layoutGetter);
833                 Layout layout = layoutGetter.get();
834                 for (LayoutTreeNode child : layout.mChildren) {
835                     child.mParent = mNavigationCursor;
836                 }
837                 int initialIndex = layout.mInitialItemIndex;
838                 if (initialIndex != -1) {
839                     mNavigationCursor.mSelectedIndex = mLayoutRows.size() + initialIndex;
840                 }
841                 addNodeListToLayoutRows(layout.mChildren);
842             } else {
843                 if (node instanceof Action && ((Action) node).mDefaultSelection) {
844                     mNavigationCursor.mSelectedIndex = mLayoutRows.size();
845                 }
846                 mLayoutRows.add(new LayoutRow(node));
847             }
848         }
849     }
850 
updateLayoutRows()851     private void updateLayoutRows() {
852         mLayoutRows.clear();
853         for (LayoutGetter layoutGetter : mVisibleLayoutGetters) {
854             layoutGetter.notVisible();
855         }
856         mVisibleLayoutGetters.clear();
857         addNodeListToLayoutRows(mNavigationCursor.mChildren);
858     }
859 
indent(int level)860     private static String indent(int level) {
861         String s = new String();
862         for (int i = 0; i < level; ++i) {
863             s += "  ";
864         }
865         return s;
866     }
867 }
868