• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.ide.eclipse.adt.internal.editors.layout.gle2;
18 
19 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
20 
21 import com.android.ide.common.api.INode;
22 import com.android.ide.common.rendering.api.RenderSession;
23 import com.android.ide.common.rendering.api.ViewInfo;
24 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
25 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
26 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
27 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
28 import com.android.util.Pair;
29 
30 import org.eclipse.swt.graphics.Rectangle;
31 import org.w3c.dom.Node;
32 
33 import java.awt.image.BufferedImage;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.RandomAccess;
42 import java.util.Set;
43 
44 /**
45  * The view hierarchy class manages a set of view info objects and performs find
46  * operations on this set.
47  */
48 public class ViewHierarchy {
49     private static final boolean DUMP_INFO = false;
50 
51     private LayoutCanvas mCanvas;
52 
53     /**
54      * Constructs a new {@link ViewHierarchy} tied to the given
55      * {@link LayoutCanvas}.
56      *
57      * @param canvas The {@link LayoutCanvas} to create a {@link ViewHierarchy}
58      *            for.
59      */
ViewHierarchy(LayoutCanvas canvas)60     public ViewHierarchy(LayoutCanvas canvas) {
61         this.mCanvas = canvas;
62     }
63 
64     /**
65      * The CanvasViewInfo root created by the last call to {@link #setSession}
66      * with a valid layout.
67      * <p/>
68      * This <em>can</em> be null to indicate we're dealing with an empty document with
69      * no root node. Null here does not mean the result was invalid, merely that the XML
70      * had no content to display -- we need to treat an empty document as valid so that
71      * we can drop new items in it.
72      */
73     private CanvasViewInfo mLastValidViewInfoRoot;
74 
75     /**
76      * True when the last {@link #setSession} provided a valid {@link LayoutScene}.
77      * <p/>
78      * When false this means the canvas is displaying an out-dated result image & bounds and some
79      * features should be disabled accordingly such a drag'n'drop.
80      * <p/>
81      * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered
82      * valid since it is an acceptable drop target.
83      */
84     private boolean mIsResultValid;
85 
86     /**
87      * A list of invisible parents (see {@link CanvasViewInfo#isInvisible()} for
88      * details) in the current view hierarchy.
89      */
90     private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>();
91 
92     /**
93      * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it
94      * reflects updates to the underlying {@link #mInvisibleParents} list.
95      */
96     private final List<CanvasViewInfo> mInvisibleParentsReadOnly =
97         Collections.unmodifiableList(mInvisibleParents);
98 
99     /**
100      * Flag which records whether or not we have any exploded parent nodes in this
101      * view hierarchy. This is used to track whether or not we need to recompute the
102      * layout when we exit show-all-invisible-parents mode (see
103      * {@link LayoutCanvas#showInvisibleViews}).
104      */
105     private boolean mExplodedParents;
106 
107     /**
108      * Bounds of included views in the current view hierarchy when rendered in other context
109      */
110     private List<Rectangle> mIncludedBounds;
111 
112     /** The render session for the current view hierarchy */
113     private RenderSession mSession;
114 
115     /** Map from nodes to canvas view infos */
116     private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap();
117 
118     /**
119      * Disposes the view hierarchy content.
120      */
dispose()121     public void dispose() {
122         if (mSession != null) {
123             mSession.dispose();
124             mSession = null;
125         }
126     }
127 
128 
129     /**
130      * Sets the result of the layout rendering. The result object indicates if the layout
131      * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
132      *
133      * Implementation detail: the bridge's computeLayout() method already returns a newly
134      * allocated ILayourResult. That means we can keep this result and hold on to it
135      * when it is valid.
136      *
137      * @param session The new session, either valid or not.
138      * @param explodedNodes The set of individual nodes the layout computer was asked to
139      *            explode. Note that these are independent of the explode-all mode where
140      *            all views are exploded; this is used only for the mode (
141      *            {@link LayoutCanvas#showInvisibleViews}) where individual invisible
142      *            nodes are padded during certain interactions.
143      */
setSession(RenderSession session, Set<UiElementNode> explodedNodes, boolean layoutlib5)144     /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes,
145             boolean layoutlib5) {
146         // replace the previous scene, so the previous scene must be disposed.
147         if (mSession != null) {
148             mSession.dispose();
149         }
150 
151         mSession = session;
152         mIsResultValid = (session != null && session.getResult().isSuccess());
153         mExplodedParents = false;
154         mNodeToView = new HashMap<UiViewElementNode, CanvasViewInfo>(50);
155         if (mIsResultValid && session != null) {
156             List<ViewInfo> rootList = session.getRootViews();
157 
158             Pair<CanvasViewInfo,List<Rectangle>> infos = null;
159 
160             if (rootList == null || rootList.size() == 0) {
161                 // Special case: Look to see if this is really an empty <merge> view,
162                 // which shows up without any ViewInfos in the merge. In that case we
163                 // want to manufacture an empty view, such that we can target the view
164                 // via drag & drop, etc.
165                 if (hasMergeRoot()) {
166                     ViewInfo mergeRoot = createMergeInfo(session);
167                     infos = CanvasViewInfo.create(mergeRoot, layoutlib5);
168                 } else {
169                     infos = null;
170                 }
171             } else {
172                 if (rootList.size() > 1 && hasMergeRoot()) {
173                     ViewInfo mergeRoot = createMergeInfo(session);
174                     mergeRoot.setChildren(rootList);
175                     infos = CanvasViewInfo.create(mergeRoot, layoutlib5);
176                 } else {
177                     ViewInfo root = rootList.get(0);
178 
179                     if (root != null) {
180                         infos = CanvasViewInfo.create(root, layoutlib5);
181                         if (DUMP_INFO) {
182                             dump(root, 0);
183                         }
184                     } else {
185                         infos = null;
186                     }
187                 }
188             }
189             if (infos != null) {
190                 mLastValidViewInfoRoot = infos.getFirst();
191                 mIncludedBounds = infos.getSecond();
192 
193                 if (mLastValidViewInfoRoot.getUiViewNode() == null &&
194                         mLastValidViewInfoRoot.getChildren().isEmpty()) {
195                     GraphicalEditorPart editor = mCanvas.getLayoutEditor().getGraphicalEditor();
196                     if (editor.getIncludedWithin() != null) {
197                         // Somehow, this view was supposed to be rendered within another
198                         // view, yet this view was rendered as part of the other view.
199                         // In that case, abort attempting to show included in; clear the
200                         // include context and trigger a standalone re-render.
201                         editor.showIn(null);
202                         return;
203                     }
204                 }
205 
206             } else {
207                 mLastValidViewInfoRoot = null;
208                 mIncludedBounds = null;
209             }
210 
211             updateNodeProxies(mLastValidViewInfoRoot);
212 
213             // Update the data structures related to tracking invisible and exploded nodes.
214             // We need to find the {@link CanvasViewInfo} objects that correspond to
215             // the passed in {@link UiElementNode} keys that were re-rendered, and mark
216             // them as exploded and store them in a list for rendering.
217             mExplodedParents = false;
218             mInvisibleParents.clear();
219             addInvisibleParents(mLastValidViewInfoRoot, explodedNodes);
220 
221             // Update the selection
222             mCanvas.getSelectionManager().sync();
223         } else {
224             mIncludedBounds = null;
225             mInvisibleParents.clear();
226         }
227     }
228 
createMergeInfo(RenderSession session)229     private ViewInfo createMergeInfo(RenderSession session) {
230         BufferedImage image = session.getImage();
231         ControlPoint imageSize = ControlPoint.create(mCanvas,
232                 mCanvas.getHorizontalTransform().getMargin() + image.getWidth(),
233                 mCanvas.getVerticalTransform().getMargin() + image.getHeight());
234         LayoutPoint layoutSize = imageSize.toLayout();
235         UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode();
236         List<UiElementNode> children = model.getUiChildren();
237         return new ViewInfo(VIEW_MERGE, children.get(0), 0, 0, layoutSize.x, layoutSize.y);
238     }
239 
240     /**
241      * Returns true if this view hierarchy corresponds to an editor that has a {@code
242      * <merge>} tag at the root
243      *
244      * @return true if there is a {@code <merge>} at the root of this editor's document
245      */
hasMergeRoot()246     private boolean hasMergeRoot() {
247         UiDocumentNode model = mCanvas.getLayoutEditor().getUiRootNode();
248         if (model != null) {
249             List<UiElementNode> children = model.getUiChildren();
250             if (children != null && children.size() > 0
251                     && VIEW_MERGE.equals(children.get(0).getDescriptor().getXmlName())) {
252                 return true;
253             }
254         }
255 
256         return false;
257     }
258 
259     /**
260      * Creates or updates the node proxy for this canvas view info.
261      * <p/>
262      * Since proxies are reused, this will update the bounds of an existing proxy when the
263      * canvas is refreshed and a view changes position or size.
264      * <p/>
265      * This is a recursive call that updates the whole hierarchy starting at the given
266      * view info.
267      */
updateNodeProxies(CanvasViewInfo vi)268     private void updateNodeProxies(CanvasViewInfo vi) {
269         if (vi == null) {
270             return;
271         }
272 
273         UiViewElementNode key = vi.getUiViewNode();
274 
275         if (key != null) {
276             mCanvas.getNodeFactory().create(vi);
277             mNodeToView.put(key, vi);
278         }
279 
280         for (CanvasViewInfo child : vi.getChildren()) {
281             updateNodeProxies(child);
282         }
283     }
284 
285     /**
286      * Make a pass over the view hierarchy and look for two things:
287      * <ol>
288      * <li>Invisible parents. These are nodes that can hold children and have empty
289      * bounds. These are then added to the {@link #mInvisibleParents} list.
290      * <li>Exploded nodes. These are nodes that were previously marked as invisible, and
291      * subsequently rendered by a recomputed layout. They now no longer have empty bounds,
292      * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we
293      * for example in selection operations can determine if we need to recompute the
294      * layout.
295      * </ol>
296      *
297      * @param vi
298      * @param invisibleNodes
299      */
addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes)300     private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) {
301         if (vi == null) {
302             return;
303         }
304 
305         if (vi.isInvisible()) {
306             mInvisibleParents.add(vi);
307         } else if (invisibleNodes != null) {
308             UiViewElementNode key = vi.getUiViewNode();
309 
310             if (key != null && invisibleNodes.contains(key)) {
311                 vi.setExploded(true);
312                 mExplodedParents = true;
313                 mInvisibleParents.add(vi);
314             }
315         }
316 
317         for (CanvasViewInfo child : vi.getChildren()) {
318             addInvisibleParents(child, invisibleNodes);
319         }
320     }
321 
322     /**
323      * Returns the current {@link RenderSession}.
324      * @return the session or null if none have been set.
325      */
getSession()326     public RenderSession getSession() {
327         return mSession;
328     }
329 
330     /**
331      * Returns true when the last {@link #setSession} provided a valid
332      * {@link RenderSession}.
333      * <p/>
334      * When false this means the canvas is displaying an out-dated result image & bounds and some
335      * features should be disabled accordingly such a drag'n'drop.
336      * <p/>
337      * Note that an empty document (with a null {@link #getRoot()}) is considered
338      * valid since it is an acceptable drop target.
339      * @return True when this {@link ViewHierarchy} contains a valid hierarchy of views.
340     */
isValid()341     public boolean isValid() {
342         return mIsResultValid;
343     }
344 
345     /**
346      * Returns true if the last valid content of the canvas represents an empty document.
347      * @return True if the last valid content of the canvas represents an empty document.
348      */
isEmpty()349     public boolean isEmpty() {
350         return mLastValidViewInfoRoot == null;
351     }
352 
353     /**
354      * Returns true if we have parents in this hierarchy that are invisible (e.g. because
355      * they have no children and zero layout bounds).
356      *
357      * @return True if we have invisible parents.
358      */
hasInvisibleParents()359     public boolean hasInvisibleParents() {
360         return mInvisibleParents.size() > 0;
361     }
362 
363     /**
364      * Returns true if we have views that were exploded during rendering
365      * @return True if we have exploded parents
366      */
hasExplodedParents()367     public boolean hasExplodedParents() {
368         return mExplodedParents;
369     }
370 
371     /** Locates and return any views that overlap the given selection rectangle.
372      * @param topLeft The top left corner of the selection rectangle.
373      * @param bottomRight The bottom right corner of the selection rectangle.
374      * @return A collection of {@link CanvasViewInfo} objects that overlap the
375      *   rectangle.
376      */
findWithin( LayoutPoint topLeft, LayoutPoint bottomRight)377     public Collection<CanvasViewInfo> findWithin(
378             LayoutPoint topLeft,
379             LayoutPoint bottomRight) {
380         Rectangle selectionRectangle = new Rectangle(topLeft.x, topLeft.y, bottomRight.x
381                 - topLeft.x, bottomRight.y - topLeft.y);
382         List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>();
383         addWithin(mLastValidViewInfoRoot, selectionRectangle, infos);
384         return infos;
385     }
386 
387     /**
388      * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly.
389      * <p/>
390      * Tries to find the inner most child matching the given x,y coordinates in the view
391      * info sub-tree. This uses the potentially-expanded selection bounds.
392      *
393      * Returns null if not found.
394      */
addWithin( CanvasViewInfo canvasViewInfo, Rectangle canvasRectangle, List<CanvasViewInfo> infos)395     private void addWithin(
396             CanvasViewInfo canvasViewInfo,
397             Rectangle canvasRectangle,
398             List<CanvasViewInfo> infos) {
399         if (canvasViewInfo == null) {
400             return;
401         }
402         Rectangle r = canvasViewInfo.getSelectionRect();
403         if (canvasRectangle.intersects(r)) {
404 
405             // try to find a matching child first
406             for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
407                 addWithin(child, canvasRectangle, infos);
408             }
409 
410             if (canvasViewInfo != mLastValidViewInfoRoot) {
411                 infos.add(canvasViewInfo);
412             }
413         }
414     }
415 
416     /**
417      * Locates and returns the {@link CanvasViewInfo} corresponding to the given
418      * node, or null if it cannot be found.
419      *
420      * @param node The node we want to find a corresponding
421      *            {@link CanvasViewInfo} for.
422      * @return The {@link CanvasViewInfo} corresponding to the given node, or
423      *         null if no match was found.
424      */
findViewInfoFor(Node node)425     public CanvasViewInfo findViewInfoFor(Node node) {
426         if (mLastValidViewInfoRoot != null) {
427             return findViewInfoForNode(node, mLastValidViewInfoRoot);
428         }
429         return null;
430     }
431 
432     /**
433      * Tries to find a child with the same view XML node in the view info sub-tree.
434      * Returns null if not found.
435      */
findViewInfoForNode(Node xmlNode, CanvasViewInfo canvasViewInfo)436     private CanvasViewInfo findViewInfoForNode(Node xmlNode, CanvasViewInfo canvasViewInfo) {
437         if (canvasViewInfo == null) {
438             return null;
439         }
440         if (canvasViewInfo.getXmlNode() == xmlNode) {
441             return canvasViewInfo;
442         }
443 
444         // Try to find a matching child
445         for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
446             CanvasViewInfo v = findViewInfoForNode(xmlNode, child);
447             if (v != null) {
448                 return v;
449             }
450         }
451 
452         return null;
453     }
454 
455 
456     /**
457      * Tries to find the inner most child matching the given x,y coordinates in
458      * the view info sub-tree, starting at the last know view info root. This
459      * uses the potentially-expanded selection bounds.
460      * <p/>
461      * Returns null if not found or if there's no view info root.
462      *
463      * @param p The point at which to look for the deepest match in the view
464      *            hierarchy
465      * @return A {@link CanvasViewInfo} that intersects the given point, or null
466      *         if nothing was found.
467      */
findViewInfoAt(LayoutPoint p)468     public CanvasViewInfo findViewInfoAt(LayoutPoint p) {
469         if (mLastValidViewInfoRoot == null) {
470             return null;
471         }
472 
473         return findViewInfoAt_Recursive(p, mLastValidViewInfoRoot);
474     }
475 
476     /**
477      * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly.
478      * <p/>
479      * Tries to find the inner most child matching the given x,y coordinates in the view
480      * info sub-tree. This uses the potentially-expanded selection bounds.
481      *
482      * Returns null if not found.
483      */
findViewInfoAt_Recursive(LayoutPoint p, CanvasViewInfo canvasViewInfo)484     private CanvasViewInfo findViewInfoAt_Recursive(LayoutPoint p, CanvasViewInfo canvasViewInfo) {
485         if (canvasViewInfo == null) {
486             return null;
487         }
488         Rectangle r = canvasViewInfo.getSelectionRect();
489         if (r.contains(p.x, p.y)) {
490 
491             // try to find a matching child first
492             // Iterate in REVERSE z order such that siblings on top
493             // are checked before earlier siblings (this matters in layouts like
494             // FrameLayout and in <merge> contexts where the views are sitting on top
495             // of each other and we want to select the same view as the one drawn
496             // on top of the others
497             List<CanvasViewInfo> children = canvasViewInfo.getChildren();
498             assert children instanceof RandomAccess;
499             for (int i = children.size() - 1; i >= 0; i--) {
500                 CanvasViewInfo child = children.get(i);
501                 CanvasViewInfo v = findViewInfoAt_Recursive(p, child);
502                 if (v != null) {
503                     return v;
504                 }
505             }
506 
507             // if no children matched, this is the view that we're looking for
508             return canvasViewInfo;
509         }
510 
511         return null;
512     }
513 
514     /**
515      * Returns a list of all the possible alternatives for a given view at the given
516      * position. This is used to build and manage the "alternate" selection that cycles
517      * around the parents or children of the currently selected element.
518      */
findAltViewInfoAt(LayoutPoint p)519     /* package */ List<CanvasViewInfo> findAltViewInfoAt(LayoutPoint p) {
520         if (mLastValidViewInfoRoot != null) {
521             return findAltViewInfoAt_Recursive(p, mLastValidViewInfoRoot, null);
522         }
523 
524         return null;
525     }
526 
527     /**
528      * Internal recursive version of {@link #findAltViewInfoAt(int, int, CanvasViewInfo)}.
529      * Please don't use directly.
530      */
findAltViewInfoAt_Recursive( LayoutPoint p, CanvasViewInfo parent, List<CanvasViewInfo> outList)531     private List<CanvasViewInfo> findAltViewInfoAt_Recursive(
532             LayoutPoint p, CanvasViewInfo parent, List<CanvasViewInfo> outList) {
533         Rectangle r;
534 
535         if (outList == null) {
536             outList = new ArrayList<CanvasViewInfo>();
537 
538             if (parent != null) {
539                 // add the parent root only once
540                 r = parent.getSelectionRect();
541                 if (r.contains(p.x, p.y)) {
542                     outList.add(parent);
543                 }
544             }
545         }
546 
547         if (parent != null && !parent.getChildren().isEmpty()) {
548             // then add all children that match the position
549             for (CanvasViewInfo child : parent.getChildren()) {
550                 r = child.getSelectionRect();
551                 if (r.contains(p.x, p.y)) {
552                     outList.add(child);
553                 }
554             }
555 
556             // finally recurse in the children
557             for (CanvasViewInfo child : parent.getChildren()) {
558                 r = child.getSelectionRect();
559                 if (r.contains(p.x, p.y)) {
560                     findAltViewInfoAt_Recursive(p, child, outList);
561                 }
562             }
563         }
564 
565         return outList;
566     }
567 
568     /**
569      * Locates and returns the {@link CanvasViewInfo} corresponding to the given
570      * node, or null if it cannot be found.
571      *
572      * @param node The node we want to find a corresponding
573      *            {@link CanvasViewInfo} for.
574      * @return The {@link CanvasViewInfo} corresponding to the given node, or
575      *         null if no match was found.
576      */
findViewInfoFor(INode node)577     public CanvasViewInfo findViewInfoFor(INode node) {
578         return findViewInfoFor((NodeProxy) node);
579     }
580 
581     /**
582      * Tries to find a child with the same view key in the view info sub-tree.
583      * Returns null if not found.
584      *
585      * @param viewKey The view key that a matching {@link CanvasViewInfo} should
586      *            have as its key.
587      * @return A {@link CanvasViewInfo} matching the given key, or null if not
588      *         found.
589      */
findViewInfoFor(UiElementNode viewKey)590     public CanvasViewInfo findViewInfoFor(UiElementNode viewKey) {
591         return mNodeToView.get(viewKey);
592     }
593 
594     /**
595      * Tries to find a child with the given node proxy as the view key.
596      * Returns null if not found.
597      *
598      * @param proxy The view key that a matching {@link CanvasViewInfo} should
599      *            have as its key.
600      * @return A {@link CanvasViewInfo} matching the given key, or null if not
601      *         found.
602      */
findViewInfoFor(NodeProxy proxy)603     public CanvasViewInfo findViewInfoFor(NodeProxy proxy) {
604         return mNodeToView.get(proxy.getNode());
605     }
606 
607     /**
608      * Returns a list of ALL ViewInfos (possibly excluding the root, depending
609      * on the parameter for that).
610      *
611      * @param includeRoot If true, include the root in the list, otherwise
612      *            exclude it (but include all its children)
613      * @return A list of canvas view infos.
614      */
findAllViewInfos(boolean includeRoot)615     public List<CanvasViewInfo> findAllViewInfos(boolean includeRoot) {
616         List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>();
617         if (mIsResultValid && mLastValidViewInfoRoot != null) {
618             findAllViewInfos(infos, mLastValidViewInfoRoot, includeRoot);
619         }
620 
621         return infos;
622     }
623 
findAllViewInfos(List<CanvasViewInfo> result, CanvasViewInfo canvasViewInfo, boolean includeRoot)624     private void findAllViewInfos(List<CanvasViewInfo> result, CanvasViewInfo canvasViewInfo,
625             boolean includeRoot) {
626         if (canvasViewInfo != null) {
627             if (includeRoot || !canvasViewInfo.isRoot()) {
628                 result.add(canvasViewInfo);
629             }
630             for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
631                 findAllViewInfos(result, child, true);
632             }
633         }
634     }
635 
636     /**
637      * Returns the root of the view hierarchy, if any (could be null, for example
638      * on rendering failure).
639      *
640      * @return The current view hierarchy, or null
641      */
getRoot()642     public CanvasViewInfo getRoot() {
643         return mLastValidViewInfoRoot;
644     }
645 
646     /**
647      * Returns a collection of views that have zero bounds and that correspond to empty
648      * parents. Note that the views may not actually have zero bounds; in particular, if
649      * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the
650      * bounds of a shown invisible node. Therefore, this method returns the views that
651      * would be invisible in a real rendering of the scene.
652      *
653      * @return A collection of empty parent views.
654      */
getInvisibleViews()655     public List<CanvasViewInfo> getInvisibleViews() {
656         return mInvisibleParentsReadOnly;
657     }
658 
659     /**
660      * Returns the invisible nodes (the {@link UiElementNode} objects corresponding
661      * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}.
662      * We are pulling out the nodes since they preserve their identity across layout
663      * rendering, and in particular we return it as a set such that the layout renderer
664      * can perform quick identity checks when looking up attribute values during the
665      * rendering process.
666      *
667      * @return A set of the invisible nodes.
668      */
getInvisibleNodes()669     public Set<UiElementNode> getInvisibleNodes() {
670         if (mInvisibleParents.size() == 0) {
671             return Collections.emptySet();
672         }
673 
674         Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size());
675         for (CanvasViewInfo info : mInvisibleParents) {
676             UiViewElementNode node = info.getUiViewNode();
677             if (node != null) {
678                 nodes.add(node);
679             }
680         }
681 
682         return nodes;
683     }
684 
685     /**
686      * Returns the list of bounds for included views in the current view hierarchy. Can be null
687      * when there are no included views.
688      *
689      * @return a list of included view bounds, or null
690      */
getIncludedBounds()691     public List<Rectangle> getIncludedBounds() {
692         return mIncludedBounds;
693     }
694 
695     /**
696      * Dumps a {@link ViewInfo} hierarchy to stdout
697      *
698      * @param info the {@link ViewInfo} object to dump
699      * @param depth the depth to indent it to
700      */
dump(ViewInfo info, int depth)701     public static void dump(ViewInfo info, int depth) {
702         if (DUMP_INFO) {
703             StringBuilder sb = new StringBuilder();
704             for (int i = 0; i < depth; i++) {
705                 sb.append("    "); //$NON-NLS-1$
706             }
707             sb.append(info.getClassName());
708             sb.append(" ["); //$NON-NLS-1$
709             sb.append(info.getLeft());
710             sb.append(","); //$NON-NLS-1$
711             sb.append(info.getTop());
712             sb.append(","); //$NON-NLS-1$
713             sb.append(info.getRight());
714             sb.append(","); //$NON-NLS-1$
715             sb.append(info.getBottom());
716             sb.append("]"); //$NON-NLS-1$
717             Object cookie = info.getCookie();
718             if (cookie instanceof UiViewElementNode) {
719                 sb.append(" "); //$NON-NLS-1$
720                 UiViewElementNode node = (UiViewElementNode) cookie;
721                 sb.append("<"); //$NON-NLS-1$
722                 sb.append(node.getDescriptor().getXmlName());
723                 sb.append(">"); //$NON-NLS-1$
724             } else if (cookie != null) {
725                 sb.append(" " + cookie); //$NON-NLS-1$
726             }
727 
728             System.out.println(sb.toString());
729 
730             for (ViewInfo child : info.getChildren()) {
731                 dump(child, depth + 1);
732             }
733         }
734     }
735 }
736