Home | History | Annotate | Download | only in gle2
      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 
     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 
     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 
     65     public static UiViewElementNode createNode(String fqn, boolean hasChildren) {
     66         return createNode(null, fqn, hasChildren);
     67     }
     68 
     69     public void testNormalCreate() throws Exception {
     70         normal(true);
     71     }
     72 
     73     public void testNormalCreateLayoutLib5() throws Exception {
     74         normal(false);
     75     }
     76 
     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 
    113     public void testShowIn() throws Exception {
    114         showIn(false);
    115     }
    116 
    117     public void testShowInLayoutLib5() throws Exception {
    118         showIn(true);
    119     }
    120 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    589     public void testIncludeBounds2() throws Exception {
    590         includeBounds2(false);
    591     }
    592 
    593     public void testIncludeBounds2LayoutLib5() throws Exception {
    594         includeBounds2(true);
    595     }
    596 
    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 
    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 
    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 
    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      */
    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)} */
    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)} */
    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