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 com.android.ide.common.rendering.api.Capability;
20 import com.android.ide.common.rendering.api.DataBindingItem;
21 import com.android.ide.common.rendering.api.MergeCookie;
22 import com.android.ide.common.rendering.api.ViewInfo;
23 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
25 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
26 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
27 import com.android.utils.Pair;
28 
29 import org.eclipse.swt.graphics.Rectangle;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Set;
39 
40 import junit.framework.TestCase;
41 
42 @SuppressWarnings("javadoc")
43 public class CanvasViewInfoTest extends TestCase {
44 
createDesc(String name, String fqn, boolean hasChildren)45     public static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) {
46         if (hasChildren) {
47             return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0],
48                     new AttributeDescriptor[0], new ElementDescriptor[1], false);
49         } else {
50             return new ViewElementDescriptor(name, fqn);
51         }
52     }
53 
createNode(UiViewElementNode parent, String fqn, boolean hasChildren)54     public static UiViewElementNode createNode(UiViewElementNode parent, String fqn,
55             boolean hasChildren) {
56         String name = fqn.substring(fqn.lastIndexOf('.') + 1);
57         ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren);
58         if (parent == null) {
59             // All node hierarchies should be wrapped inside a document node at the root
60             parent = new UiViewElementNode(createDesc("doc", "doc", true));
61         }
62         return (UiViewElementNode) parent.appendNewUiChild(descriptor);
63     }
64 
createNode(String fqn, boolean hasChildren)65     public static UiViewElementNode createNode(String fqn, boolean hasChildren) {
66         return createNode(null, fqn, hasChildren);
67     }
68 
testNormalCreate()69     public void testNormalCreate() throws Exception {
70         normal(true);
71     }
72 
testNormalCreateLayoutLib5()73     public void testNormalCreateLayoutLib5() throws Exception {
74         normal(false);
75     }
76 
normal(boolean layoutlib5)77     private void normal(boolean layoutlib5) {
78 
79         // Normal view hierarchy, no null keys anywhere
80 
81         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
82         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
83         UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
84         ViewInfo child1 = new ViewInfo("Button", child1Node, 0, 0, 50, 20);
85         UiViewElementNode child2Node = createNode(rootNode, "android.widget.Button", false);
86         ViewInfo child2 = new ViewInfo("Button", child2Node, 0, 20, 70, 25);
87         root.setChildren(Arrays.asList(child1, child2));
88 
89         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
90         assertNotNull(rootView);
91         assertEquals("LinearLayout", rootView.getName());
92         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
93         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
94         assertNull(rootView.getParent());
95         assertSame(rootView.getUiViewNode(), rootNode);
96         assertEquals(2, rootView.getChildren().size());
97         CanvasViewInfo childView1 = rootView.getChildren().get(0);
98         CanvasViewInfo childView2 = rootView.getChildren().get(1);
99 
100         assertEquals("Button", childView1.getName());
101         assertSame(rootView, childView1.getParent());
102         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
103         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
104         assertSame(childView1.getUiViewNode(), child1Node);
105 
106         assertEquals("Button", childView2.getName());
107         assertSame(rootView, childView2.getParent());
108         assertEquals(new Rectangle(10, 30, 69, 4), childView2.getAbsRect());
109         assertEquals(new Rectangle(10, 30, 69, 5), childView2.getSelectionRect());
110         assertSame(childView2.getUiViewNode(), child2Node);
111     }
112 
testShowIn()113     public void testShowIn() throws Exception {
114         showIn(false);
115     }
116 
testShowInLayoutLib5()117     public void testShowInLayoutLib5() throws Exception {
118         showIn(true);
119     }
120 
showIn(boolean layoutlib5)121     public void showIn(boolean layoutlib5) throws Exception {
122 
123         // Test rendering of "Show Included In" (included content rendered
124         // within an outer content that has null keys)
125 
126         ViewInfo root = new ViewInfo("LinearLayout", null, 10, 10, 100, 100);
127         ViewInfo child1 = new ViewInfo("CheckBox", null, 0, 0, 50, 20);
128         UiViewElementNode child2Node = createNode("android.widget.RelativeLayout", true);
129         ViewInfo child2 = new ViewInfo("RelativeLayout", child2Node, 0, 20, 70, 25);
130         root.setChildren(Arrays.asList(child1, child2));
131         UiViewElementNode child21Node = createNode("android.widget.Button", false);
132         ViewInfo child21 = new ViewInfo("RadioButton", child21Node, 0, 20, 70, 25);
133         child2.setChildren(Arrays.asList(child21));
134 
135         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
136         assertNotNull(rootView);
137         assertEquals("LinearLayout", rootView.getName());
138         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
139         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
140         assertNull(rootView.getParent());
141         assertNull(rootView.getUiViewNode());
142         assertEquals(1, rootView.getChildren().size());
143         CanvasViewInfo includedView = rootView.getChildren().get(0);
144 
145         assertEquals("RelativeLayout", includedView.getName());
146         assertSame(rootView, includedView.getParent());
147         assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
148         assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
149         assertSame(includedView.getUiViewNode(), child2Node);
150 
151         CanvasViewInfo grandChild = includedView.getChildren().get(0);
152         assertNotNull(grandChild);
153         assertEquals("RadioButton", grandChild.getName());
154         assertSame(child21Node, grandChild.getUiViewNode());
155         assertEquals(new Rectangle(10, 50, 69, 4), grandChild.getAbsRect());
156         assertEquals(new Rectangle(10, 50, 69, 5), grandChild.getSelectionRect());
157     }
158 
testIncludeTag()159     public void testIncludeTag() throws Exception {
160         boolean layoutlib5 = true;
161 
162         // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag)
163 
164         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
165         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
166         UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
167         ViewInfo child1 = new ViewInfo("CheckBox", child1Node, 0, 0, 50, 20);
168         UiViewElementNode child2Node = createNode(rootNode, "include", true);
169         ViewInfo child2 = new ViewInfo("RelativeLayout", child2Node, 0, 20, 70, 25);
170         root.setChildren(Arrays.asList(child1, child2));
171         ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
172         child2.setChildren(Arrays.asList(child21));
173 
174         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
175         assertNotNull(rootView);
176         assertEquals("LinearLayout", rootView.getName());
177         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
178         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
179         assertNull(rootView.getParent());
180         assertSame(rootNode, rootView.getUiViewNode());
181         assertEquals(2, rootView.getChildren().size());
182 
183         CanvasViewInfo childView1 = rootView.getChildren().get(0);
184         CanvasViewInfo includedView = rootView.getChildren().get(1);
185 
186         assertEquals("CheckBox", childView1.getName());
187         assertSame(rootView, childView1.getParent());
188         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
189         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
190         assertSame(childView1.getUiViewNode(), child1Node);
191 
192         assertEquals("RelativeLayout", includedView.getName());
193         assertSame(rootView, includedView.getParent());
194         assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
195         assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
196         assertSame(includedView.getUiViewNode(), child2Node);
197         assertEquals(0, includedView.getChildren().size());
198     }
199 
testNoIncludeTag()200     public void testNoIncludeTag() throws Exception {
201         boolean layoutlib5 = false;
202 
203         // Test rendering of included views on layoutlib 4- (e.g. no <include> tag cookie
204         // in view info)
205 
206         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
207         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
208         UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
209         ViewInfo child1 = new ViewInfo("CheckBox", child1Node, 0, 0, 50, 20);
210         UiViewElementNode child2Node = createNode(rootNode, "include", true);
211         ViewInfo child2 = new ViewInfo("RelativeLayout", null /* layoutlib 4 */, 0, 20, 70, 25);
212         root.setChildren(Arrays.asList(child1, child2));
213         ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
214         child2.setChildren(Arrays.asList(child21));
215 
216         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
217         assertNotNull(rootView);
218         assertEquals("LinearLayout", rootView.getName());
219         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
220         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
221         assertNull(rootView.getParent());
222         assertSame(rootNode, rootView.getUiViewNode());
223         assertEquals(2, rootView.getChildren().size());
224 
225         CanvasViewInfo childView1 = rootView.getChildren().get(0);
226         CanvasViewInfo includedView = rootView.getChildren().get(1);
227 
228         assertEquals("CheckBox", childView1.getName());
229         assertSame(rootView, childView1.getParent());
230         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
231         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
232         assertSame(childView1.getUiViewNode(), child1Node);
233 
234         assertEquals("RelativeLayout", includedView.getName());
235         assertSame(rootView, includedView.getParent());
236         assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
237         assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
238         assertSame(includedView.getUiViewNode(), child2Node);
239         assertEquals(0, includedView.getChildren().size());
240     }
241 
testMergeMatching()242     public void testMergeMatching() throws Exception {
243         boolean layoutlib5 = false;
244 
245         // Test rendering of MULTIPLE included views or when there is no simple match
246         // between view info and ui element node children
247 
248         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
249         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
250         UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
251         ViewInfo child1 = new ViewInfo("CheckBox", child1Node, 0, 0, 50, 20);
252         UiViewElementNode multiChildNode1 = createNode(rootNode, "foo", true);
253         UiViewElementNode multiChildNode2 = createNode(rootNode, "bar", true);
254         ViewInfo child2 = new ViewInfo("RelativeLayout", null, 0, 20, 70, 25);
255         ViewInfo child3 = new ViewInfo("AbsoluteLayout", null, 10, 40, 50, 15);
256         root.setChildren(Arrays.asList(child1, child2, child3));
257         ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
258         child2.setChildren(Arrays.asList(child21));
259 
260         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
261         assertNotNull(rootView);
262         assertEquals("LinearLayout", rootView.getName());
263         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
264         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
265         assertNull(rootView.getParent());
266         assertTrue(rootView.isRoot());
267         assertSame(rootNode, rootView.getUiViewNode());
268         assertEquals(3, rootView.getChildren().size());
269 
270         CanvasViewInfo childView1 = rootView.getChildren().get(0);
271         assertFalse(childView1.isRoot());
272         CanvasViewInfo includedView1 = rootView.getChildren().get(1);
273         assertFalse(includedView1.isRoot());
274         CanvasViewInfo includedView2 = rootView.getChildren().get(2);
275         assertFalse(includedView1.isRoot());
276 
277         assertEquals("CheckBox", childView1.getName());
278         assertSame(rootView, childView1.getParent());
279         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
280         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
281         assertSame(childView1.getUiViewNode(), child1Node);
282 
283         assertEquals("RelativeLayout", includedView1.getName());
284         assertSame(multiChildNode1, includedView1.getUiViewNode());
285         assertEquals("foo", includedView1.getUiViewNode().getDescriptor().getXmlName());
286         assertSame(multiChildNode2, includedView2.getUiViewNode());
287         assertEquals("AbsoluteLayout", includedView2.getName());
288         assertEquals("bar", includedView2.getUiViewNode().getDescriptor().getXmlName());
289         assertSame(rootView, includedView1.getParent());
290         assertSame(rootView, includedView2.getParent());
291         assertEquals(new Rectangle(10, 30, 69, 4), includedView1.getAbsRect());
292         assertEquals(new Rectangle(10, 30, 69, 5), includedView1.getSelectionRect());
293         assertEquals(new Rectangle(20, 50, 39, -26), includedView2.getAbsRect());
294         assertEquals(new Rectangle(20, 35, 39, 5), includedView2.getSelectionRect());
295         assertEquals(0, includedView1.getChildren().size());
296         assertEquals(0, includedView2.getChildren().size());
297     }
298 
testMerge()299     public void testMerge() throws Exception {
300         boolean layoutlib5 = false;
301 
302         // Test rendering of MULTIPLE included views or when there is no simple match
303         // between view info and ui element node children
304 
305         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
306         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100);
307         UiViewElementNode child1Node = createNode(rootNode, "android.widget.Button", false);
308         ViewInfo child1 = new ViewInfo("CheckBox", child1Node, 0, 0, 50, 20);
309         UiViewElementNode multiChildNode = createNode(rootNode, "foo", true);
310         ViewInfo child2 = new ViewInfo("RelativeLayout", null, 0, 20, 70, 25);
311         ViewInfo child3 = new ViewInfo("AbsoluteLayout", null, 10, 40, 50, 15);
312         root.setChildren(Arrays.asList(child1, child2, child3));
313         ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25);
314         child2.setChildren(Arrays.asList(child21));
315 
316         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
317         assertNotNull(rootView);
318         assertEquals("LinearLayout", rootView.getName());
319         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
320         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
321         assertNull(rootView.getParent());
322         assertSame(rootNode, rootView.getUiViewNode());
323         assertEquals(2, rootView.getChildren().size());
324 
325         CanvasViewInfo childView1 = rootView.getChildren().get(0);
326         CanvasViewInfo includedView = rootView.getChildren().get(1);
327 
328         assertEquals("CheckBox", childView1.getName());
329         assertSame(rootView, childView1.getParent());
330         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getAbsRect());
331         assertEquals(new Rectangle(10, 10, 49, 19), childView1.getSelectionRect());
332         assertSame(childView1.getUiViewNode(), child1Node);
333 
334         assertEquals("RelativeLayout", includedView.getName());
335         assertSame(rootView, includedView.getParent());
336         assertEquals(new Rectangle(10, 30, 69, 4), includedView.getAbsRect());
337         assertEquals(new Rectangle(10, 30, 69, 5), includedView.getSelectionRect());
338         assertEquals(0, includedView.getChildren().size());
339         assertSame(multiChildNode, includedView.getUiViewNode());
340     }
341 
testInsertMerge()342     public void testInsertMerge() throws Exception {
343         boolean layoutlib5 = false;
344 
345         // Test rendering of MULTIPLE included views or when there is no simple match
346         // between view info and ui element node children
347 
348         UiViewElementNode mergeNode = createNode("merge", true);
349         UiViewElementNode rootNode = createNode(mergeNode, "android.widget.Button", false);
350         ViewInfo root = new ViewInfo("Button", rootNode, 10, 10, 100, 100);
351 
352         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
353         assertNotNull(rootView);
354         assertEquals("merge", rootView.getName());
355         assertSame(rootView.getUiViewNode(), mergeNode);
356         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect());
357         assertEquals(new Rectangle(10, 10, 89, 89), rootView.getSelectionRect());
358         assertNull(rootView.getParent());
359         assertSame(mergeNode, rootView.getUiViewNode());
360         assertEquals(1, rootView.getChildren().size());
361 
362         CanvasViewInfo childView1 = rootView.getChildren().get(0);
363 
364         assertEquals("Button", childView1.getName());
365         assertSame(rootView, childView1.getParent());
366         assertEquals(new Rectangle(10, 10, 89, 89), childView1.getAbsRect());
367         assertEquals(new Rectangle(10, 10, 89, 89), childView1.getSelectionRect());
368         assertSame(childView1.getUiViewNode(), rootNode);
369     }
370 
testUnmatchedMissing()371     public void testUnmatchedMissing() throws Exception {
372         boolean layoutlib5 = false;
373 
374         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
375         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
376         List<ViewInfo> children = new ArrayList<ViewInfo>();
377         // Should be matched up with corresponding node:
378         Set<Integer> missingKeys = new HashSet<Integer>();
379         // Should not be matched with any views, but should get view created:
380         Set<Integer> extraKeys = new HashSet<Integer>();
381         // Should not be matched with any nodes
382         Set<Integer> extraViews = new HashSet<Integer>();
383         int numViews = 30;
384         missingKeys.add(0);
385         missingKeys.add(4);
386         missingKeys.add(14);
387         missingKeys.add(29);
388         extraKeys.add(9);
389         extraKeys.add(20);
390         extraKeys.add(22);
391         extraViews.add(18);
392         extraViews.add(24);
393 
394         List<String> expectedViewNames = new ArrayList<String>();
395         List<String> expectedNodeNames = new ArrayList<String>();
396 
397         for (int i = 0; i < numViews; i++) {
398             UiViewElementNode childNode = null;
399             if (!extraViews.contains(i)) {
400                 childNode = createNode(rootNode, "childNode" + i, false);
401             }
402             Object cookie = missingKeys.contains(i) || extraViews.contains(i) ? null : childNode;
403             ViewInfo childView = new ViewInfo("childView" + i, cookie,
404                     0, i * 20, 50, (i + 1) * 20);
405             children.add(childView);
406 
407             if (!extraViews.contains(i)) {
408                 expectedViewNames.add("childView" + i);
409                 expectedNodeNames.add("childNode" + i);
410             }
411 
412             if (extraKeys.contains(i)) {
413                 createNode(rootNode, "extraNodeAt" + i, false);
414 
415                 expectedViewNames.add("extraNodeAt" + i);
416                 expectedNodeNames.add("extraNodeAt" + i);
417             }
418         }
419         root.setChildren(children);
420 
421         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
422         assertNotNull(rootView);
423 
424         // dump(root, 0);
425         // dump(rootView, 0);
426 
427         assertEquals("LinearLayout", rootView.getName());
428         assertNull(rootView.getParent());
429         assertSame(rootNode, rootView.getUiViewNode());
430         assertEquals(numViews + extraKeys.size() - extraViews.size(), rootNode.getUiChildren()
431                 .size());
432         assertEquals(numViews + extraKeys.size() - extraViews.size(),
433                 rootView.getChildren().size());
434         assertEquals(expectedViewNames.size(), rootView.getChildren().size());
435         for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
436             CanvasViewInfo childView = rootView.getChildren().get(i);
437             String expectedViewName = expectedViewNames.get(i);
438             String expectedNodeName = expectedNodeNames.get(i);
439             assertEquals(expectedViewName, childView.getName());
440             assertNotNull(childView.getUiViewNode());
441             assertEquals(expectedNodeName, childView.getUiViewNode().getDescriptor().getXmlName());
442         }
443     }
444 
testMergeCookies()445     public void testMergeCookies() throws Exception {
446         boolean layoutlib5 = true;
447 
448         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
449         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
450 
451         // Create the merge cookies in the opposite order to ensure that we don't
452         // apply our own logic when matching up views with nodes
453         LinkedList<MergeCookie> cookies = new LinkedList<MergeCookie>();
454         for (int i = 0; i < 10; i++) {
455             UiViewElementNode node = createNode(rootNode, "childNode" + i, false);
456             cookies.addFirst(new MergeCookie(node));
457         }
458         Iterator<MergeCookie> it = cookies.iterator();
459         ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
460         for (int i = 0; i < 10; i++) {
461             ViewInfo childView = new ViewInfo("childView" + i, it.next(), 0, i * 20, 50,
462                     (i + 1) * 20);
463             children.add(childView);
464         }
465         root.setChildren(children);
466 
467         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
468         assertNotNull(rootView);
469 
470         assertEquals("LinearLayout", rootView.getName());
471         assertNull(rootView.getParent());
472         assertSame(rootNode, rootView.getUiViewNode());
473         for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
474             CanvasViewInfo childView = rootView.getChildren().get(i);
475             assertEquals("childView" + i, childView.getName());
476             assertEquals("childNode" + (9 - i), childView.getUiViewNode().getDescriptor()
477                     .getXmlName());
478         }
479     }
480 
testMergeCookies2()481     public void testMergeCookies2() throws Exception {
482         boolean layoutlib5 = true;
483 
484         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
485         ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100);
486 
487         UiViewElementNode node1 = createNode(rootNode, "childNode1", false);
488         UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
489         MergeCookie cookie1 = new MergeCookie(node1);
490         MergeCookie cookie2 = new MergeCookie(node2);
491 
492         // Sets alternating merge cookies and checks whether the node sibling lists are
493         // okay and merged correctly
494 
495         ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
496         for (int i = 0; i < 10; i++) {
497             Object cookie = (i % 2) == 0 ? cookie1 : cookie2;
498             ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50,
499                     (i + 1) * 20);
500             children.add(childView);
501         }
502         root.setChildren(children);
503 
504         Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5);
505         CanvasViewInfo rootView = result.getFirst();
506         List<Rectangle> bounds = result.getSecond();
507         assertNull(bounds);
508         assertNotNull(rootView);
509 
510         assertEquals("LinearLayout", rootView.getName());
511         assertNull(rootView.getParent());
512         assertSame(rootNode, rootView.getUiViewNode());
513         assertEquals(10, rootView.getChildren().size());
514         assertEquals(2, rootView.getUniqueChildren().size());
515         for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
516             CanvasViewInfo childView = rootView.getChildren().get(i);
517             assertEquals("childView" + i, childView.getName());
518             Object cookie = (i % 2) == 0 ? node1 : node2;
519             assertSame(cookie, childView.getUiViewNode());
520             List<CanvasViewInfo> nodeSiblings = childView.getNodeSiblings();
521             assertEquals(5, nodeSiblings.size());
522         }
523         List<CanvasViewInfo> nodeSiblings = rootView.getChildren().get(0).getNodeSiblings();
524         for (int j = 0; j < 5; j++) {
525             assertEquals("childView" + (j * 2), nodeSiblings.get(j).getName());
526         }
527         nodeSiblings = rootView.getChildren().get(1).getNodeSiblings();
528         for (int j = 0; j < 5; j++) {
529             assertEquals("childView" + (j * 2 + 1), nodeSiblings.get(j).getName());
530         }
531     }
532 
testIncludeBounds()533     public void testIncludeBounds() throws Exception {
534         boolean layoutlib5 = true;
535 
536         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
537         ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100);
538 
539         UiViewElementNode node1 = createNode(rootNode, "childNode1", false);
540         UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
541         MergeCookie cookie1 = new MergeCookie(node1);
542         MergeCookie cookie2 = new MergeCookie(node2);
543 
544         // Sets alternating merge cookies and checks whether the node sibling lists are
545         // okay and merged correctly
546 
547         ArrayList<ViewInfo> children = new ArrayList<ViewInfo>();
548         for (int i = 0; i < 10; i++) {
549             Object cookie = (i % 2) == 0 ? cookie1 : cookie2;
550             ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50,
551                     (i + 1) * 20);
552             children.add(childView);
553         }
554         root.setChildren(children);
555 
556         Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5);
557         CanvasViewInfo rootView = result.getFirst();
558         List<Rectangle> bounds = result.getSecond();
559         assertNotNull(rootView);
560 
561         assertEquals("included", rootView.getName());
562         assertNull(rootView.getParent());
563         assertNull(rootView.getUiViewNode());
564         assertEquals(10, rootView.getChildren().size());
565         assertEquals(2, rootView.getUniqueChildren().size());
566         for (int i = 0, n = rootView.getChildren().size(); i < n; i++) {
567             CanvasViewInfo childView = rootView.getChildren().get(i);
568             assertEquals("childView" + i, childView.getName());
569             Object cookie = (i % 2) == 0 ? node1 : node2;
570             assertSame(cookie, childView.getUiViewNode());
571             List<CanvasViewInfo> nodeSiblings = childView.getNodeSiblings();
572             assertEquals(5, nodeSiblings.size());
573         }
574         List<CanvasViewInfo> nodeSiblings = rootView.getChildren().get(0).getNodeSiblings();
575         for (int j = 0; j < 5; j++) {
576             assertEquals("childView" + (j * 2), nodeSiblings.get(j).getName());
577         }
578         nodeSiblings = rootView.getChildren().get(1).getNodeSiblings();
579         for (int j = 0; j < 5; j++) {
580             assertEquals("childView" + (j * 2 + 1), nodeSiblings.get(j).getName());
581         }
582 
583         // Only show the primary bounds as included
584         assertEquals(2, bounds.size());
585         assertEquals(new Rectangle(0, 0, 49, 19), bounds.get(0));
586         assertEquals(new Rectangle(0, 20, 49, 19), bounds.get(1));
587     }
588 
testIncludeBounds2()589     public void testIncludeBounds2() throws Exception {
590         includeBounds2(false);
591     }
592 
testIncludeBounds2LayoutLib5()593     public void testIncludeBounds2LayoutLib5() throws Exception {
594         includeBounds2(true);
595     }
596 
includeBounds2(boolean layoutlib5)597     public void includeBounds2(boolean layoutlib5) throws Exception {
598 
599         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
600         ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100);
601 
602         UiViewElementNode node1 = createNode(rootNode, "childNode1", false);
603         UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
604 
605         ViewInfo childView1 = new ViewInfo("childView1", node1, 0, 20, 50, 40);
606         ViewInfo childView2 = new ViewInfo("childView2", node2, 0, 40, 50, 60);
607 
608         root.setChildren(Arrays.asList(childView1, childView2));
609 
610         Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5);
611         CanvasViewInfo rootView = result.getFirst();
612         List<Rectangle> bounds = result.getSecond();
613         assertNotNull(rootView);
614 
615         assertEquals("included", rootView.getName());
616         assertNull(rootView.getParent());
617         assertNull(rootView.getUiViewNode());
618         assertEquals(2, rootView.getChildren().size());
619         assertEquals(2, rootView.getUniqueChildren().size());
620 
621         Rectangle bounds1 = bounds.get(0);
622         Rectangle bounds2 = bounds.get(1);
623         assertEquals(new Rectangle(0, 20, 49, 19), bounds1);
624         assertEquals(new Rectangle(0, 40, 49, 19), bounds2);
625     }
626 
testCookieWorkaround()627     public void testCookieWorkaround() throws Exception {
628         UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true);
629         ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100);
630 
631         UiViewElementNode node2 = createNode(rootNode, "childNode2", false);
632         MergeCookie mergeCookie = new MergeCookie(root);
633 
634         ViewInfo childView1 = new ViewInfo("childView1", mergeCookie, 0, 20, 50, 40);
635         ViewInfo childView2 = new ViewInfo("childView2", node2, 0, 40, 50, 60);
636 
637         root.setChildren(Arrays.asList(childView1, childView2));
638 
639         Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, true);
640         CanvasViewInfo rootView = result.getFirst();
641         List<Rectangle> bounds = result.getSecond();
642         assertNotNull(rootView);
643 
644         assertEquals("included", rootView.getName());
645         assertNull(rootView.getParent());
646         assertNull(rootView.getUiViewNode());
647         // childView1 should have been removed since it has the wrong merge cookie
648         assertEquals(1, rootView.getChildren().size());
649         assertEquals(1, rootView.getUniqueChildren().size());
650 
651         Rectangle bounds1 = bounds.get(0);
652         assertEquals(new Rectangle(0, 40, 49, 19), bounds1);
653     }
654 
testGestureOverlayView()655     public void testGestureOverlayView() throws Exception {
656         boolean layoutlib5 = true;
657 
658         // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag)
659 
660         UiViewElementNode rootNode = createNode("android.gesture.GestureOverlayView", true);
661         UiViewElementNode childNode = createNode(rootNode, "android.widget.LinearLayout", false);
662         UiViewElementNode grandChildNode = createNode(childNode, "android.widget.Button", false);
663         ViewInfo root = new ViewInfo("GestureOverlayView", rootNode, 10, 10, 100, 100);
664         ViewInfo child = new ViewInfo("LinearLayout", childNode, 0, 0, 50, 20);
665         root.setChildren(Collections.singletonList(child));
666         ViewInfo grandChild = new ViewInfo("Button", grandChildNode, 0, 20, 70, 25);
667         child.setChildren(Collections.singletonList(grandChild));
668         CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst();
669         assertNotNull(rootView);
670         assertEquals("GestureOverlayView", rootView.getName());
671 
672         assertTrue(rootView.isRoot());
673         assertNull(rootView.getParent());
674         assertSame(rootNode, rootView.getUiViewNode());
675         assertEquals(1, rootView.getChildren().size());
676 
677         CanvasViewInfo childView = rootView.getChildren().get(0);
678         assertEquals("LinearLayout", childView.getName());
679 
680         // This should also be a root for the special case that the root is
681         assertTrue(childView.isRoot());
682 
683         assertEquals(1, childView.getChildren().size());
684         CanvasViewInfo grandChildView = childView.getChildren().get(0);
685         assertEquals("Button", grandChildView.getName());
686         assertFalse(grandChildView.isRoot());
687     }
688 
testListView()689     public void testListView() throws Exception {
690         // For ListViews we get AdapterItemReferences as cookies. Ensure that this
691         // works properly.
692         //
693         // android.widget.FrameLayout [0,50,320,480] <FrameLayout>
694         //     android.widget.ListView [0,0,320,430] <ListView>
695         //        android.widget.LinearLayout [0,0,320,17] SessionParams$AdapterItemReference
696         //            android.widget.TextView [0,0,73,17]
697         //        android.widget.LinearLayout [0,18,320,35] SessionParams$AdapterItemReference
698         //            android.widget.TextView [0,0,73,17]
699         //        android.widget.LinearLayout [0,36,320,53] SessionParams$AdapterItemReference
700         //            android.widget.TextView [0,0,73,17]
701         //        ...
702 
703         UiViewElementNode rootNode = createNode("FrameLayout", true);
704         UiViewElementNode childNode = createNode(rootNode, "ListView", false);
705         /*UiViewElementNode grandChildNode =*/ createNode(childNode, "LinearLayout", false);
706         /*UiViewElementNode greatGrandChildNode =*/ createNode(childNode, "TextView", false);
707         DataBindingItem dataBindingItem = new DataBindingItem("foo");
708 
709         ViewInfo root = new ViewInfo("FrameLayout", rootNode, 0, 50, 320, 480);
710         ViewInfo child = new ViewInfo("ListView", childNode, 0, 0, 320, 430);
711         root.setChildren(Collections.singletonList(child));
712         ViewInfo grandChild = new ViewInfo("LinearLayout", dataBindingItem, 0, 0, 320, 17);
713         child.setChildren(Collections.singletonList(grandChild));
714         ViewInfo greatGrandChild = new ViewInfo("Button", null, 0, 0, 73, 17);
715         grandChild.setChildren(Collections.singletonList(greatGrandChild));
716         CanvasViewInfo rootView = CanvasViewInfo.create(root, true /*layoutlib5*/).getFirst();
717         assertNotNull(rootView);
718 
719         assertEquals("FrameLayout", rootView.getName());
720         assertEquals(1, rootView.getChildren().size());
721         assertSame(rootNode, rootView.getUiViewNode());
722 
723         CanvasViewInfo childView = rootView.getChildren().get(0);
724         assertEquals("ListView", childView.getName());
725         assertEquals(0, childView.getChildren().size());
726         assertSame(childNode, childView.getUiViewNode());
727     }
728 
729     /**
730      * Dumps out the given {@link ViewInfo} hierarchy to standard out.
731      * Useful during development.
732      *
733      * @param graphicalEditor the editor associated with this hierarchy
734      * @param root the root of the {@link ViewInfo} hierarchy
735      */
dump(GraphicalEditorPart graphicalEditor, ViewInfo root)736     public static void dump(GraphicalEditorPart graphicalEditor, ViewInfo root) {
737         System.out.println("\n\nRendering:");
738         boolean supportsEmbedding = graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT);
739         System.out.println("Supports Embedded Layout=" + supportsEmbedding);
740         System.out.println("Rendering context=" + graphicalEditor.getIncludedWithin());
741         dump(root, 0);
742     }
743 
744     /** Helper for {@link #dump(GraphicalEditorPart, ViewInfo)} */
dump(ViewInfo info, int depth)745     public static void dump(ViewInfo info, int depth) {
746         StringBuilder sb = new StringBuilder();
747         for (int i = 0; i < depth; i++) {
748             sb.append("    ");
749         }
750         sb.append(info.getClassName());
751         sb.append(" [");
752         sb.append(info.getLeft());
753         sb.append(",");
754         sb.append(info.getTop());
755         sb.append(",");
756         sb.append(info.getRight());
757         sb.append(",");
758         sb.append(info.getBottom());
759         sb.append("] ");
760         Object cookie = info.getCookie();
761         if (cookie instanceof UiViewElementNode) {
762             sb.append(" ");
763             UiViewElementNode node = (UiViewElementNode) cookie;
764             sb.append("<");
765             sb.append(node.getDescriptor().getXmlName());
766             sb.append("> ");
767         } else if (cookie != null) {
768             sb.append(" cookie=" + cookie);
769         }
770 
771         System.out.println(sb.toString());
772 
773         for (ViewInfo child : info.getChildren()) {
774             dump(child, depth + 1);
775         }
776     }
777 
778     /** Helper for {@link #dump(GraphicalEditorPart, ViewInfo)} */
dump(CanvasViewInfo info, int depth)779     public static void dump(CanvasViewInfo info, int depth) {
780         StringBuilder sb = new StringBuilder();
781         for (int i = 0; i < depth; i++) {
782             sb.append("    ");
783         }
784         sb.append(info.getName());
785         sb.append(" [");
786         sb.append(info.getAbsRect());
787         sb.append("], node=");
788         sb.append(info.getUiViewNode());
789 
790         System.out.println(sb.toString());
791 
792         for (CanvasViewInfo child : info.getChildren()) {
793             dump(child, depth + 1);
794         }
795     }
796 
797 }
798