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