• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.gre;
18 
19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
20 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
21 import static com.android.ide.common.layout.LayoutConstants.FQCN_BUTTON;
22 import static com.android.ide.common.layout.LayoutConstants.FQCN_SPINNER;
23 import static com.android.ide.common.layout.LayoutConstants.FQCN_TOGGLE_BUTTON;
24 import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
25 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
26 
27 import com.android.annotations.VisibleForTesting;
28 import com.android.ide.common.api.IViewMetadata.FillPreference;
29 import com.android.ide.common.api.Margins;
30 import com.android.ide.common.api.ResizePolicy;
31 import com.android.ide.eclipse.adt.AdtPlugin;
32 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
33 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
34 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
35 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
36 import com.android.resources.Density;
37 import com.android.util.Pair;
38 
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43 import org.xml.sax.InputSource;
44 
45 import java.io.BufferedInputStream;
46 import java.io.InputStream;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.Map;
56 import java.util.Set;
57 
58 import javax.xml.parsers.DocumentBuilder;
59 import javax.xml.parsers.DocumentBuilderFactory;
60 
61 /**
62  * The {@link ViewMetadataRepository} contains additional metadata for Android view
63  * classes
64  */
65 public class ViewMetadataRepository {
66     private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml";  //$NON-NLS-1$
67     private static final String METADATA_FILENAME = "extra-view-metadata.xml";  //$NON-NLS-1$
68 
69     /** Singleton instance */
70     private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
71 
72     /**
73      * Returns the singleton instance
74      *
75      * @return the {@link ViewMetadataRepository}
76      */
get()77     public static ViewMetadataRepository get() {
78         return sInstance;
79     }
80 
81     /**
82      * Ever increasing counter used to assign natural ordering numbers to views and
83      * categories
84      */
85     private static int sNextOrdinal = 0;
86 
87     /**
88      * List of categories (which contain views); constructed lazily so use
89      * {@link #getCategories()}
90      */
91     private List<CategoryData> mCategories;
92 
93     /**
94      * Map from class names to view data objects; constructed lazily so use
95      * {@link #getClassToView}
96      */
97     private Map<String, ViewData> mClassToView;
98 
99     /** Hidden constructor: Create via factory {@link #get()} instead */
ViewMetadataRepository()100     private ViewMetadataRepository() {
101     }
102 
103     /** Returns a map from class fully qualified names to {@link ViewData} objects */
getClassToView()104     private Map<String, ViewData> getClassToView() {
105         if (mClassToView == null) {
106             int initialSize = 75;
107             mClassToView = new HashMap<String, ViewData>(initialSize);
108             List<CategoryData> categories = getCategories();
109             for (CategoryData category : categories) {
110                 for (ViewData view : category) {
111                     mClassToView.put(view.getFcqn(), view);
112                 }
113             }
114             assert mClassToView.size() <= initialSize;
115         }
116 
117         return mClassToView;
118     }
119 
120     /**
121      * Returns an XML document containing rendering configurations for the various Android
122      * views. The FQN of each view can be obtained via the
123      * {@link #getFullClassName(Element)} method
124      *
125      * @return an XML document containing rendering elements
126      */
getRenderingConfigDoc()127     public Document getRenderingConfigDoc() {
128         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
129         Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
130         InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
131         InputSource is = new InputSource(paletteStream);
132         try {
133             factory.setNamespaceAware(true);
134             factory.setValidating(false);
135             factory.setIgnoringComments(true);
136             DocumentBuilder builder = factory.newDocumentBuilder();
137             return builder.parse(is);
138         } catch (Exception e) {
139             AdtPlugin.log(e, "Parsing palette file failed");
140             return null;
141         }
142     }
143 
144     /**
145      * Returns a fully qualified class name for an element in the rendering document
146      * returned by {@link #getRenderingConfigDoc()}
147      *
148      * @param element the element to look up the fqcn for
149      * @return the fqcn of the view the element represents a preview for
150      */
getFullClassName(Element element)151     public String getFullClassName(Element element) {
152         // We don't use the element tag name, because in some cases we have
153         // an outer element to render some interesting inner element, such as a tab widget
154         // (which must be rendered inside a tab host).
155         //
156         // Therefore, we instead use the convention that the id is the fully qualified
157         // class name, with .'s replaced with _'s.
158 
159         // Special case: for tab host we aren't allowed to mess with the id
160         String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
161 
162         if ("@android:id/tabhost".equals(id)) {
163             // Special case to distinguish TabHost and TabWidget
164             NodeList children = element.getChildNodes();
165             if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
166                 Element child = (Element) children.item(1);
167                 String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
168                 if ("@+id/android_widget_TabWidget".equals(childId)) {
169                     return "android.widget.TabWidget"; // TODO: Tab widget!
170                 }
171             }
172             return "android.widget.TabHost"; // TODO: Tab widget!
173         }
174 
175         StringBuilder sb = new StringBuilder();
176         int i = 0;
177         if (id.startsWith(NEW_ID_PREFIX)) {
178             i = NEW_ID_PREFIX.length();
179         } else if (id.startsWith(ID_PREFIX)) {
180             i = ID_PREFIX.length();
181         }
182 
183         for (; i < id.length(); i++) {
184             char c = id.charAt(i);
185             if (c == '_') {
186                 sb.append('.');
187             } else {
188                 sb.append(c);
189             }
190         }
191 
192         return sb.toString();
193     }
194 
195     /** Returns an ordered list of categories and views, parsed from a metadata file */
getCategories()196     private List<CategoryData> getCategories() {
197         if (mCategories == null) {
198             mCategories = new ArrayList<CategoryData>();
199 
200             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
201             Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
202             InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
203             InputSource is = new InputSource(new BufferedInputStream(inputStream));
204             try {
205                 factory.setNamespaceAware(true);
206                 factory.setValidating(false);
207                 factory.setIgnoringComments(true);
208                 DocumentBuilder builder = factory.newDocumentBuilder();
209                 Document document = builder.parse(is);
210                 Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
211                 for (FillPreference pref : FillPreference.values()) {
212                     fillTypes.put(pref.toString().toLowerCase(Locale.US), pref);
213                 }
214 
215                 NodeList categoryNodes = document.getDocumentElement().getChildNodes();
216                 for (int i = 0, n = categoryNodes.getLength(); i < n; i++) {
217                     Node node = categoryNodes.item(i);
218                     if (node.getNodeType() == Node.ELEMENT_NODE) {
219                         Element element = (Element) node;
220                         if (element.getNodeName().equals("category")) { //$NON-NLS-1$
221                             String name = element.getAttribute("name"); //$NON-NLS-1$
222                             CategoryData category = new CategoryData(name);
223                             NodeList children = element.getChildNodes();
224                             for (int j = 0, m = children.getLength(); j < m; j++) {
225                                 Node childNode = children.item(j);
226                                 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
227                                     Element child = (Element) childNode;
228                                     ViewData view = createViewData(fillTypes, child,
229                                             null, FillPreference.NONE, RenderMode.NORMAL, null);
230                                     category.addView(view);
231                                 }
232                             }
233                             mCategories.add(category);
234                         }
235                     }
236                 }
237             } catch (Exception e) {
238                 AdtPlugin.log(e, "Invalid palette metadata"); //$NON-NLS-1$
239             }
240         }
241 
242         return mCategories;
243     }
244 
createViewData(Map<String, FillPreference> fillTypes, Element child, String defaultFqcn, FillPreference defaultFill, RenderMode defaultRender, String defaultSize)245     private ViewData createViewData(Map<String, FillPreference> fillTypes,
246             Element child, String defaultFqcn, FillPreference defaultFill,
247             RenderMode defaultRender, String defaultSize) {
248         String fqcn = child.getAttribute("class"); //$NON-NLS-1$
249         if (fqcn.length() == 0) {
250             fqcn = defaultFqcn;
251         }
252         String fill = child.getAttribute("fill"); //$NON-NLS-1$
253         FillPreference fillPreference = null;
254         if (fill.length() > 0) {
255             fillPreference = fillTypes.get(fill);
256         }
257         if (fillPreference == null) {
258             fillPreference = defaultFill;
259         }
260         String skip = child.getAttribute("skip"); //$NON-NLS-1$
261         RenderMode renderMode = defaultRender;
262         String render = child.getAttribute("render"); //$NON-NLS-1$
263         if (render.length() > 0) {
264             renderMode = RenderMode.get(render);
265         }
266         String displayName = child.getAttribute("name"); //$NON-NLS-1$
267         if (displayName.length() == 0) {
268             displayName = null;
269         }
270 
271         String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$
272         String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$
273         String resize = child.getAttribute("resize"); //$NON-NLS-1$
274         ViewData view = new ViewData(fqcn, displayName, fillPreference,
275                 skip.length() == 0 ? false : Boolean.valueOf(skip),
276                 renderMode, relatedTo, resize, topAttrs);
277 
278         String init = child.getAttribute("init"); //$NON-NLS-1$
279         String icon = child.getAttribute("icon"); //$NON-NLS-1$
280 
281         view.setInitString(init);
282         if (icon.length() > 0) {
283             view.setIconName(icon);
284         }
285 
286         // Nested variations?
287         if (child.hasChildNodes()) {
288             // Palette variations
289             NodeList childNodes = child.getChildNodes();
290             for (int k = 0, kl = childNodes.getLength(); k < kl; k++) {
291                 Node variationNode = childNodes.item(k);
292                 if (variationNode.getNodeType() == Node.ELEMENT_NODE) {
293                     Element variation = (Element) variationNode;
294                     ViewData variationView = createViewData(fillTypes, variation,
295                             fqcn, fillPreference, renderMode, resize);
296                     view.addVariation(variationView);
297                 }
298             }
299         }
300 
301         return view;
302     }
303 
304     /**
305      * Computes the palette entries for the given {@link AndroidTargetData}, looking up the
306      * available node descriptors, categorizing and sorting them.
307      *
308      * @param targetData the target data for which to compute palette entries
309      * @param alphabetical if true, sort all items in alphabetical order
310      * @param createCategories if true, organize the items into categories
311      * @return a list of pairs where each pair contains of the category label and an
312      *         ordered list of elements to be included in that category
313      */
getPaletteEntries( AndroidTargetData targetData, boolean alphabetical, boolean createCategories)314     public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
315             AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
316         List<Pair<String, List<ViewElementDescriptor>>> result =
317             new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
318 
319         List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2);
320         LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
321         lists.add(layoutDescriptors.getViewDescriptors());
322         lists.add(layoutDescriptors.getLayoutDescriptors());
323 
324         // First record map of FQCN to ViewElementDescriptor such that we can quickly
325         // determine if a particular palette entry is available
326         Map<String, ViewElementDescriptor> fqcnToDescriptor =
327             new HashMap<String, ViewElementDescriptor>();
328         for (List<ViewElementDescriptor> list : lists) {
329             for (ViewElementDescriptor view : list) {
330                 String fqcn = view.getFullClassName();
331                 if (fqcn == null) {
332                     // <view> and <merge> tags etc
333                     fqcn = view.getUiName();
334                 }
335                 fqcnToDescriptor.put(fqcn, view);
336             }
337         }
338 
339         Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>(
340                 layoutDescriptors.getViewDescriptors().size()
341                 + layoutDescriptors.getLayoutDescriptors().size());
342         remaining.addAll(layoutDescriptors.getViewDescriptors());
343         remaining.addAll(layoutDescriptors.getLayoutDescriptors());
344 
345         // Now iterate in palette metadata order over the items in the palette and include
346         // any that also appear as a descriptor
347         List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>();
348         for (CategoryData category : getCategories()) {
349             if (createCategories) {
350                 categoryItems = new ArrayList<ViewElementDescriptor>();
351             }
352             for (ViewData view : category) {
353                 String fqcn = view.getFcqn();
354                 ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn);
355                 if (descriptor != null) {
356                     remaining.remove(descriptor);
357                     if (view.getSkip()) {
358                         continue;
359                     }
360 
361                     if (view.getDisplayName() != null || view.getInitString().length() > 0) {
362                         categoryItems.add(new PaletteMetadataDescriptor(descriptor,
363                                 view.getDisplayName(), view.getInitString(), view.getIconName()));
364                     } else {
365                         categoryItems.add(descriptor);
366                     }
367 
368                     if (view.hasVariations()) {
369                         for (ViewData variation : view.getVariations()) {
370                             String init = variation.getInitString();
371                             String icon = variation.getIconName();
372                             ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor,
373                                     variation.getDisplayName(), init, icon);
374                             categoryItems.add(desc);
375                         }
376                     }
377                 }
378             }
379 
380             if (createCategories && categoryItems.size() > 0) {
381                 if (alphabetical) {
382                     Collections.sort(categoryItems);
383                 }
384                 result.add(Pair.of(category.getName(), categoryItems));
385             }
386         }
387 
388         if (remaining.size() > 0) {
389             List<ViewElementDescriptor> otherItems =
390                     new ArrayList<ViewElementDescriptor>(remaining);
391             // Always sorted, we don't have a natural order for these unknowns
392             Collections.sort(otherItems);
393             if (createCategories) {
394                 result.add(Pair.of("Other", otherItems));
395             } else {
396                 categoryItems.addAll(otherItems);
397             }
398         }
399 
400         if (!createCategories) {
401             if (alphabetical) {
402                 Collections.sort(categoryItems);
403             }
404             result.add(Pair.of("Views", categoryItems));
405         }
406 
407         return result;
408     }
409 
410     @VisibleForTesting
getAllFqcns()411     Collection<String> getAllFqcns() {
412         return getClassToView().keySet();
413     }
414 
415     /**
416      * Metadata holder for a particular category - contains the name of the category, its
417      * ordinal (for natural/logical sorting order) and views contained in the category
418      */
419     private static class CategoryData implements Iterable<ViewData>, Comparable<CategoryData> {
420         /** Category name */
421         private final String mName;
422         /** Views included in this category */
423         private final List<ViewData> mViews = new ArrayList<ViewData>();
424         /** Natural ordering rank */
425         private final int mOrdinal = sNextOrdinal++;
426 
427         /** Constructs a new category with the given name */
CategoryData(String name)428         private CategoryData(String name) {
429             super();
430             mName = name;
431         }
432 
433         /** Adds a new view into this category */
addView(ViewData view)434         private void addView(ViewData view) {
435             mViews.add(view);
436         }
437 
getName()438         private String getName() {
439             return mName;
440         }
441 
442         // Implements Iterable<ViewData> such that we can use for-each on the category to
443         // enumerate its views
444         @Override
iterator()445         public Iterator<ViewData> iterator() {
446             return mViews.iterator();
447         }
448 
449         // Implements Comparable<CategoryData> such that categories can be naturally sorted
450         @Override
compareTo(CategoryData other)451         public int compareTo(CategoryData other) {
452             return mOrdinal - other.mOrdinal;
453         }
454     }
455 
456     /** Metadata holder for a view of a given fully qualified class name */
457     private static class ViewData implements Comparable<ViewData> {
458         /** The fully qualified class name of the view */
459         private final String mFqcn;
460         /** Fill preference of the view */
461         private final FillPreference mFillPreference;
462         /** Skip this item in the palette? */
463         private final boolean mSkip;
464         /** Must this item be rendered alone? skipped? etc */
465         private final RenderMode mRenderMode;
466         /** Related views */
467         private final String mRelatedTo;
468         /** The relative rank of the view for natural ordering */
469         private final int mOrdinal = sNextOrdinal++;
470         /** List of optional variations */
471         private List<ViewData> mVariations;
472         /** Display name. Can be null. */
473         private String mDisplayName;
474         /**
475          * Optional initialization string - a comma separate set of name/value pairs to
476          * initialize the element with
477          */
478         private String mInitString;
479         /** The name of an icon (known to the {@link IconFactory} to show for this view */
480         private String mIconName;
481         /** The resize preference of this view */
482         private String mResize;
483         /** The most commonly set attributes of this view */
484         private String mTopAttrs;
485 
486         /** Constructs a new view data for the given class */
ViewData(String fqcn, String displayName, FillPreference fillPreference, boolean skip, RenderMode renderMode, String relatedTo, String resize, String topAttrs)487         private ViewData(String fqcn, String displayName,
488                 FillPreference fillPreference, boolean skip, RenderMode renderMode,
489                 String relatedTo, String resize, String topAttrs) {
490             super();
491             mFqcn = fqcn;
492             mDisplayName = displayName;
493             mFillPreference = fillPreference;
494             mSkip = skip;
495             mRenderMode = renderMode;
496             mRelatedTo = relatedTo;
497             mResize = resize;
498             mTopAttrs = topAttrs;
499         }
500 
501         /** Returns the {@link FillPreference} for views of this type */
getFillPreference()502         private FillPreference getFillPreference() {
503             return mFillPreference;
504         }
505 
506         /** Fully qualified class name of views of this type */
getFcqn()507         private String getFcqn() {
508             return mFqcn;
509         }
510 
getDisplayName()511         private String getDisplayName() {
512             return mDisplayName;
513         }
514 
getResize()515         private String getResize() {
516             return mResize;
517         }
518 
519         // Implements Comparable<ViewData> such that views can be sorted naturally
520         @Override
compareTo(ViewData other)521         public int compareTo(ViewData other) {
522             return mOrdinal - other.mOrdinal;
523         }
524 
getRenderMode()525         public RenderMode getRenderMode() {
526             return mRenderMode;
527         }
528 
getSkip()529         public boolean getSkip() {
530             return mSkip;
531         }
532 
getRelatedTo()533         public List<String> getRelatedTo() {
534             if (mRelatedTo == null || mRelatedTo.length() == 0) {
535                 return Collections.emptyList();
536             } else {
537                 String[] basenames = mRelatedTo.split(","); //$NON-NLS-1$
538                 List<String> result = new ArrayList<String>();
539                 ViewMetadataRepository repository = ViewMetadataRepository.get();
540                 Map<String, ViewData> classToView = repository.getClassToView();
541 
542                 List<String> fqns = new ArrayList<String>(classToView.keySet());
543                 for (String basename : basenames) {
544                     boolean found = false;
545                     for (String fqcn : fqns) {
546                         String suffix = '.' + basename;
547                         if (fqcn.endsWith(suffix)) {
548                             result.add(fqcn);
549                             found = true;
550                             break;
551                         }
552                     }
553                     assert found : basename;
554                 }
555 
556                 return result;
557             }
558         }
559 
getTopAttributes()560         public List<String> getTopAttributes() {
561             // "id" is a top attribute for all views, so it is not included in the XML, we just
562             // add it in dynamically here
563             if (mTopAttrs == null || mTopAttrs.length() == 0) {
564                 return Collections.singletonList(ATTR_ID);
565             } else {
566                 String[] split = mTopAttrs.split(","); //$NON-NLS-1$
567                 List<String> topAttributes = new ArrayList<String>(split.length + 1);
568                 topAttributes.add(ATTR_ID);
569                 for (int i = 0, n = split.length; i < n; i++) {
570                     topAttributes.add(split[i]);
571                 }
572                 return Collections.<String>unmodifiableList(topAttributes);
573             }
574         }
575 
addVariation(ViewData variation)576         void addVariation(ViewData variation) {
577             if (mVariations == null) {
578                 mVariations = new ArrayList<ViewData>(4);
579             }
580             mVariations.add(variation);
581         }
582 
getVariations()583         List<ViewData> getVariations() {
584             return mVariations;
585         }
586 
hasVariations()587         boolean hasVariations() {
588             return mVariations != null && mVariations.size() > 0;
589         }
590 
setInitString(String initString)591         private void setInitString(String initString) {
592             this.mInitString = initString;
593         }
594 
getInitString()595         private String getInitString() {
596             return mInitString;
597         }
598 
setIconName(String iconName)599         private void setIconName(String iconName) {
600             this.mIconName = iconName;
601         }
602 
getIconName()603         private String getIconName() {
604             return mIconName;
605         }
606     }
607 
608     /**
609      * Returns the {@link FillPreference} for classes with the given fully qualified class
610      * name
611      *
612      * @param fqcn the fully qualified class name of the view
613      * @return a suitable {@link FillPreference} for the given view type
614      */
getFillPreference(String fqcn)615     public FillPreference getFillPreference(String fqcn) {
616         ViewData view = getClassToView().get(fqcn);
617         if (view != null) {
618             return view.getFillPreference();
619         }
620 
621         return FillPreference.NONE;
622     }
623 
624     /**
625      * Returns the {@link RenderMode} for classes with the given fully qualified class
626      * name
627      *
628      * @param fqcn the fully qualified class name
629      * @return the {@link RenderMode} to use for previews of the given view type
630      */
getRenderMode(String fqcn)631     public RenderMode getRenderMode(String fqcn) {
632         ViewData view = getClassToView().get(fqcn);
633         if (view != null) {
634             return view.getRenderMode();
635         }
636 
637         return RenderMode.NORMAL;
638     }
639 
640     /**
641      * Returns the {@link ResizePolicy} for the given class.
642      *
643      * @param fqcn the fully qualified class name of the target widget
644      * @return the {@link ResizePolicy} for the widget, which will never be null (but may
645      *         be the default of {@link ResizePolicy#full()} if no metadata is found for
646      *         the given widget)
647      */
getResizePolicy(String fqcn)648     public ResizePolicy getResizePolicy(String fqcn) {
649         ViewData view = getClassToView().get(fqcn);
650         if (view != null) {
651             String resize = view.getResize();
652             if (resize != null && resize.length() > 0) {
653                 if ("full".equals(resize)) { //$NON-NLS-1$
654                     return ResizePolicy.full();
655                 } else if ("none".equals(resize)) { //$NON-NLS-1$
656                     return ResizePolicy.none();
657                 } else if ("horizontal".equals(resize)) { //$NON-NLS-1$
658                     return ResizePolicy.horizontal();
659                 } else if ("vertical".equals(resize)) { //$NON-NLS-1$
660                     return ResizePolicy.vertical();
661                 } else if ("scaled".equals(resize)) { //$NON-NLS-1$
662                     return ResizePolicy.scaled();
663                 } else {
664                     assert false : resize;
665                 }
666             }
667         }
668 
669         return ResizePolicy.full();
670     }
671 
672     /**
673      * Returns true if classes with the given fully qualified class name should be hidden
674      * or skipped from the palette
675      *
676      * @param fqcn the fully qualified class name
677      * @return true if views of the given type should be hidden from the palette
678      */
getSkip(String fqcn)679     public boolean getSkip(String fqcn) {
680         ViewData view = getClassToView().get(fqcn);
681         if (view != null) {
682             return view.getSkip();
683         }
684 
685         return false;
686     }
687 
688     /**
689      * Returns a list of the top (most commonly set) attributes of the given
690      * view.
691      *
692      * @param fqcn the fully qualified class name
693      * @return a list, never null but possibly empty, of popular attribute names
694      *         (not including a namespace prefix)
695      */
getTopAttributes(String fqcn)696     public List<String> getTopAttributes(String fqcn) {
697         ViewData view = getClassToView().get(fqcn);
698         if (view != null) {
699             return view.getTopAttributes();
700         }
701 
702         return Collections.singletonList(ATTR_ID);
703     }
704 
705     /**
706      * Returns a set of fully qualified names for views that are closely related to the
707      * given view
708      *
709      * @param fqcn the fully qualified class name
710      * @return a list, never null but possibly empty, of views that are related to the
711      *         view of the given type
712      */
getRelatedTo(String fqcn)713     public List<String> getRelatedTo(String fqcn) {
714         ViewData view = getClassToView().get(fqcn);
715         if (view != null) {
716             return view.getRelatedTo();
717         }
718 
719         return Collections.emptyList();
720     }
721 
722     /** Render mode for palette preview */
723     public enum RenderMode {
724         /**
725          * Render previews, and it can be rendered as a sibling of many other views in a
726          * big linear layout
727          */
728         NORMAL,
729         /** This view needs to be rendered alone */
730         ALONE,
731         /**
732          * Skip this element; it doesn't work or does not produce any visible artifacts
733          * (such as the basic layouts)
734          */
735         SKIP;
736 
737         /**
738          * Returns the {@link RenderMode} for the given render XML attribute
739          * value
740          *
741          * @param render the attribute value in the metadata XML file
742          * @return a corresponding {@link RenderMode}, never null
743          */
get(String render)744         public static RenderMode get(String render) {
745             if ("alone".equals(render)) {       //$NON-NLS-1$
746                 return ALONE;
747             } else if ("skip".equals(render)) { //$NON-NLS-1$
748                 return SKIP;
749             } else {
750                 return NORMAL;
751             }
752         }
753     }
754 
755     /**
756      * Are insets supported yet? This flag indicates whether the {@link #getInsets} method
757      * can return valid data, such that clients can avoid doing any work computing the
758      * current theme or density if there's no chance that valid insets will be returned
759      */
760     public static final boolean INSETS_SUPPORTED = false;
761 
762     /**
763      * Returns the insets of widgets with the given fully qualified name, in the given
764      * theme and the given screen density.
765      *
766      * @param fqcn the fully qualified name of the view
767      * @param density the screen density
768      * @param theme the theme name
769      * @return the insets of the visual bounds relative to the view info bounds, or null
770      *         if not known or if there are no insets
771      */
getInsets(String fqcn, Density density, String theme)772     public static Margins getInsets(String fqcn, Density density, String theme) {
773         if (INSETS_SUPPORTED) {
774             // Some sample data measured manually for common themes and widgets.
775             if (fqcn.equals(FQCN_BUTTON)) {
776                 if (density == Density.HIGH) {
777                     if (theme.startsWith(HOLO_PREFIX)) {
778                         // Theme.Holo, Theme.Holo.Light, WVGA
779                         return new Margins(5, 5, 5, 5);
780                     } else {
781                         // Theme.Light, WVGA
782                         return new Margins(4, 4, 0, 7);
783                     }
784                 } else if (density == Density.MEDIUM) {
785                     if (theme.startsWith(HOLO_PREFIX)) {
786                         // Theme.Holo, Theme.Holo.Light, WVGA
787                         return new Margins(3, 3, 3, 3);
788                     } else {
789                         // Theme.Light, HVGA
790                         return new Margins(2, 2, 0, 4);
791                     }
792                 } else if (density == Density.LOW) {
793                     if (theme.startsWith(HOLO_PREFIX)) {
794                         // Theme.Holo, Theme.Holo.Light, QVGA
795                         return new Margins(2, 2, 2, 2);
796                     } else {
797                         // Theme.Light, QVGA
798                         return new Margins(1, 3, 0, 4);
799                     }
800                 }
801             } else if (fqcn.equals(FQCN_TOGGLE_BUTTON)) {
802                 if (density == Density.HIGH) {
803                     if (theme.startsWith(HOLO_PREFIX)) {
804                         // Theme.Holo, Theme.Holo.Light, WVGA
805                         return new Margins(5, 5, 5, 5);
806                     } else {
807                         // Theme.Light, WVGA
808                         return new Margins(2, 2, 0, 5);
809                     }
810                 } else if (density == Density.MEDIUM) {
811                     if (theme.startsWith(HOLO_PREFIX)) {
812                         // Theme.Holo, Theme.Holo.Light, WVGA
813                         return new Margins(3, 3, 3, 3);
814                     } else {
815                         // Theme.Light, HVGA
816                         return new Margins(0, 1, 0, 3);
817                     }
818                 } else if (density == Density.LOW) {
819                     if (theme.startsWith(HOLO_PREFIX)) {
820                         // Theme.Holo, Theme.Holo.Light, QVGA
821                         return new Margins(2, 2, 2, 2);
822                     } else {
823                         // Theme.Light, QVGA
824                         return new Margins(2, 2, 0, 4);
825                     }
826                 }
827             } else if (fqcn.equals(FQCN_SPINNER)) {
828                 if (density == Density.HIGH) {
829                     if (!theme.startsWith(HOLO_PREFIX)) {
830                         // Theme.Light, WVGA
831                         return new Margins(3, 4, 2, 8);
832                     } // Doesn't render on Holo!
833                 } else if (density == Density.MEDIUM) {
834                     if (!theme.startsWith(HOLO_PREFIX)) {
835                         // Theme.Light, HVGA
836                         return new Margins(1, 1, 0, 4);
837                     }
838                 }
839             }
840         }
841 
842         return null;
843     }
844 
845     private static final String HOLO_PREFIX = "Theme.Holo"; //$NON-NLS-1$
846 }
847