blob: 5f2659ef284afc55bf3298d7b062357b9edf8411 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gre;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.FQCN_BUTTON;
import static com.android.SdkConstants.FQCN_SPINNER;
import static com.android.SdkConstants.FQCN_TOGGLE_BUTTON;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.api.IViewMetadata.FillPreference;
import com.android.ide.common.api.Margins;
import com.android.ide.common.api.ResizePolicy;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.Density;
import com.android.utils.Pair;
import com.google.common.base.Splitter;
import com.google.common.io.Closeables;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
/**
* The {@link ViewMetadataRepository} contains additional metadata for Android view
* classes
*/
public class ViewMetadataRepository {
private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml"; //$NON-NLS-1$
private static final String METADATA_FILENAME = "extra-view-metadata.xml"; //$NON-NLS-1$
/** Singleton instance */
private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
/**
* Returns the singleton instance
*
* @return the {@link ViewMetadataRepository}
*/
public static ViewMetadataRepository get() {
return sInstance;
}
/**
* Ever increasing counter used to assign natural ordering numbers to views and
* categories
*/
private static int sNextOrdinal = 0;
/**
* List of categories (which contain views); constructed lazily so use
* {@link #getCategories()}
*/
private List<CategoryData> mCategories;
/**
* Map from class names to view data objects; constructed lazily so use
* {@link #getClassToView}
*/
private Map<String, ViewData> mClassToView;
/** Hidden constructor: Create via factory {@link #get()} instead */
private ViewMetadataRepository() {
}
/** Returns a map from class fully qualified names to {@link ViewData} objects */
private Map<String, ViewData> getClassToView() {
if (mClassToView == null) {
int initialSize = 75;
mClassToView = new HashMap<String, ViewData>(initialSize);
List<CategoryData> categories = getCategories();
for (CategoryData category : categories) {
for (ViewData view : category) {
mClassToView.put(view.getFcqn(), view);
}
}
assert mClassToView.size() <= initialSize;
}
return mClassToView;
}
/**
* Returns an XML document containing rendering configurations for the various Android
* views. The FQN of each view can be obtained via the
* {@link #getFullClassName(Element)} method
*
* @return an XML document containing rendering elements
*/
public Document getRenderingConfigDoc() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
InputSource is = new InputSource(paletteStream);
try {
factory.setNamespaceAware(true);
factory.setValidating(false);
factory.setIgnoringComments(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(is);
} catch (Exception e) {
AdtPlugin.log(e, "Parsing palette file failed");
return null;
} finally {
Closeables.closeQuietly(paletteStream);
}
}
/**
* Returns a fully qualified class name for an element in the rendering document
* returned by {@link #getRenderingConfigDoc()}
*
* @param element the element to look up the fqcn for
* @return the fqcn of the view the element represents a preview for
*/
public String getFullClassName(Element element) {
// We don't use the element tag name, because in some cases we have
// an outer element to render some interesting inner element, such as a tab widget
// (which must be rendered inside a tab host).
//
// Therefore, we instead use the convention that the id is the fully qualified
// class name, with .'s replaced with _'s.
// Special case: for tab host we aren't allowed to mess with the id
String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
if ("@android:id/tabhost".equals(id)) {
// Special case to distinguish TabHost and TabWidget
NodeList children = element.getChildNodes();
if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
Element child = (Element) children.item(1);
String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
if ("@+id/android_widget_TabWidget".equals(childId)) {
return "android.widget.TabWidget"; // TODO: Tab widget!
}
}
return "android.widget.TabHost"; // TODO: Tab widget!
}
StringBuilder sb = new StringBuilder();
int i = 0;
if (id.startsWith(NEW_ID_PREFIX)) {
i = NEW_ID_PREFIX.length();
} else if (id.startsWith(ID_PREFIX)) {
i = ID_PREFIX.length();
}
for (; i < id.length(); i++) {
char c = id.charAt(i);
if (c == '_') {
sb.append('.');
} else {
sb.append(c);
}
}
return sb.toString();
}
/** Returns an ordered list of categories and views, parsed from a metadata file */
@SuppressWarnings("resource") // streams passed to parser InputSource closed by parser
private List<CategoryData> getCategories() {
if (mCategories == null) {
mCategories = new ArrayList<CategoryData>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
InputSource is = new InputSource(new BufferedInputStream(inputStream));
try {
factory.setNamespaceAware(true);
factory.setValidating(false);
factory.setIgnoringComments(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(is);
Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
for (FillPreference pref : FillPreference.values()) {
fillTypes.put(pref.toString().toLowerCase(Locale.US), pref);
}
NodeList categoryNodes = document.getDocumentElement().getChildNodes();
for (int i = 0, n = categoryNodes.getLength(); i < n; i++) {
Node node = categoryNodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
if (element.getNodeName().equals("category")) { //$NON-NLS-1$
String name = element.getAttribute("name"); //$NON-NLS-1$
CategoryData category = new CategoryData(name);
NodeList children = element.getChildNodes();
for (int j = 0, m = children.getLength(); j < m; j++) {
Node childNode = children.item(j);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element) childNode;
ViewData view = createViewData(fillTypes, child,
null, FillPreference.NONE, RenderMode.NORMAL, null);
category.addView(view);
}
}
mCategories.add(category);
}
}
}
} catch (Exception e) {
AdtPlugin.log(e, "Invalid palette metadata"); //$NON-NLS-1$
}
}
return mCategories;
}
private ViewData createViewData(Map<String, FillPreference> fillTypes,
Element child, String defaultFqcn, FillPreference defaultFill,
RenderMode defaultRender, String defaultSize) {
String fqcn = child.getAttribute("class"); //$NON-NLS-1$
if (fqcn.length() == 0) {
fqcn = defaultFqcn;
}
String fill = child.getAttribute("fill"); //$NON-NLS-1$
FillPreference fillPreference = null;
if (fill.length() > 0) {
fillPreference = fillTypes.get(fill);
}
if (fillPreference == null) {
fillPreference = defaultFill;
}
String skip = child.getAttribute("skip"); //$NON-NLS-1$
RenderMode renderMode = defaultRender;
String render = child.getAttribute("render"); //$NON-NLS-1$
if (render.length() > 0) {
renderMode = RenderMode.get(render);
}
String displayName = child.getAttribute("name"); //$NON-NLS-1$
if (displayName.length() == 0) {
displayName = null;
}
String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$
String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$
String resize = child.getAttribute("resize"); //$NON-NLS-1$
ViewData view = new ViewData(fqcn, displayName, fillPreference,
skip.length() == 0 ? false : Boolean.valueOf(skip),
renderMode, relatedTo, resize, topAttrs);
String init = child.getAttribute("init"); //$NON-NLS-1$
String icon = child.getAttribute("icon"); //$NON-NLS-1$
view.setInitString(init);
if (icon.length() > 0) {
view.setIconName(icon);
}
// Nested variations?
if (child.hasChildNodes()) {
// Palette variations
NodeList childNodes = child.getChildNodes();
for (int k = 0, kl = childNodes.getLength(); k < kl; k++) {
Node variationNode = childNodes.item(k);
if (variationNode.getNodeType() == Node.ELEMENT_NODE) {
Element variation = (Element) variationNode;
ViewData variationView = createViewData(fillTypes, variation,
fqcn, fillPreference, renderMode, resize);
view.addVariation(variationView);
}
}
}
return view;
}
/**
* Computes the palette entries for the given {@link AndroidTargetData}, looking up the
* available node descriptors, categorizing and sorting them.
*
* @param targetData the target data for which to compute palette entries
* @param alphabetical if true, sort all items in alphabetical order
* @param createCategories if true, organize the items into categories
* @return a list of pairs where each pair contains of the category label and an
* ordered list of elements to be included in that category
*/
public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
List<Pair<String, List<ViewElementDescriptor>>> result =
new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2);
LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
lists.add(layoutDescriptors.getViewDescriptors());
lists.add(layoutDescriptors.getLayoutDescriptors());
// First record map of FQCN to ViewElementDescriptor such that we can quickly
// determine if a particular palette entry is available
Map<String, ViewElementDescriptor> fqcnToDescriptor =
new HashMap<String, ViewElementDescriptor>();
for (List<ViewElementDescriptor> list : lists) {
for (ViewElementDescriptor view : list) {
String fqcn = view.getFullClassName();
if (fqcn == null) {
// <view> and <merge> tags etc
fqcn = view.getUiName();
}
fqcnToDescriptor.put(fqcn, view);
}
}
Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>(
layoutDescriptors.getViewDescriptors().size()
+ layoutDescriptors.getLayoutDescriptors().size());
remaining.addAll(layoutDescriptors.getViewDescriptors());
remaining.addAll(layoutDescriptors.getLayoutDescriptors());
// Now iterate in palette metadata order over the items in the palette and include
// any that also appear as a descriptor
List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>();
for (CategoryData category : getCategories()) {
if (createCategories) {
categoryItems = new ArrayList<ViewElementDescriptor>();
}
for (ViewData view : category) {
String fqcn = view.getFcqn();
ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn);
if (descriptor != null) {
remaining.remove(descriptor);
if (view.getSkip()) {
continue;
}
if (view.getDisplayName() != null || view.getInitString().length() > 0) {
categoryItems.add(new PaletteMetadataDescriptor(descriptor,
view.getDisplayName(), view.getInitString(), view.getIconName()));
} else {
categoryItems.add(descriptor);
}
if (view.hasVariations()) {
for (ViewData variation : view.getVariations()) {
String init = variation.getInitString();
String icon = variation.getIconName();
ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor,
variation.getDisplayName(), init, icon);
categoryItems.add(desc);
}
}
}
}
if (createCategories && categoryItems.size() > 0) {
if (alphabetical) {
Collections.sort(categoryItems);
}
result.add(Pair.of(category.getName(), categoryItems));
}
}
if (remaining.size() > 0) {
List<ViewElementDescriptor> otherItems =
new ArrayList<ViewElementDescriptor>(remaining);
// Always sorted, we don't have a natural order for these unknowns
Collections.sort(otherItems);
if (createCategories) {
result.add(Pair.of("Other", otherItems));
} else {
categoryItems.addAll(otherItems);
}
}
if (!createCategories) {
if (alphabetical) {
Collections.sort(categoryItems);
}
result.add(Pair.of("Views", categoryItems));
}
return result;
}
@VisibleForTesting
Collection<String> getAllFqcns() {
return getClassToView().keySet();
}
/**
* Metadata holder for a particular category - contains the name of the category, its
* ordinal (for natural/logical sorting order) and views contained in the category
*/
private static class CategoryData implements Iterable<ViewData>, Comparable<CategoryData> {
/** Category name */
private final String mName;
/** Views included in this category */
private final List<ViewData> mViews = new ArrayList<ViewData>();
/** Natural ordering rank */
private final int mOrdinal = sNextOrdinal++;
/** Constructs a new category with the given name */
private CategoryData(String name) {
super();
mName = name;
}
/** Adds a new view into this category */
private void addView(ViewData view) {
mViews.add(view);
}
private String getName() {
return mName;
}
// Implements Iterable<ViewData> such that we can use for-each on the category to
// enumerate its views
@Override
public Iterator<ViewData> iterator() {
return mViews.iterator();
}
// Implements Comparable<CategoryData> such that categories can be naturally sorted
@Override
public int compareTo(CategoryData other) {
return mOrdinal - other.mOrdinal;
}
}
/** Metadata holder for a view of a given fully qualified class name */
private static class ViewData implements Comparable<ViewData> {
/** The fully qualified class name of the view */
private final String mFqcn;
/** Fill preference of the view */
private final FillPreference mFillPreference;
/** Skip this item in the palette? */
private final boolean mSkip;
/** Must this item be rendered alone? skipped? etc */
private final RenderMode mRenderMode;
/** Related views */
private final String mRelatedTo;
/** The relative rank of the view for natural ordering */
private final int mOrdinal = sNextOrdinal++;
/** List of optional variations */
private List<ViewData> mVariations;
/** Display name. Can be null. */
private String mDisplayName;
/**
* Optional initialization string - a comma separate set of name/value pairs to
* initialize the element with
*/
private String mInitString;
/** The name of an icon (known to the {@link IconFactory} to show for this view */
private String mIconName;
/** The resize preference of this view */
private String mResize;
/** The most commonly set attributes of this view */
private String mTopAttrs;
/** Constructs a new view data for the given class */
private ViewData(String fqcn, String displayName,
FillPreference fillPreference, boolean skip, RenderMode renderMode,
String relatedTo, String resize, String topAttrs) {
super();
mFqcn = fqcn;
mDisplayName = displayName;
mFillPreference = fillPreference;
mSkip = skip;
mRenderMode = renderMode;
mRelatedTo = relatedTo;
mResize = resize;
mTopAttrs = topAttrs;
}
/** Returns the {@link FillPreference} for views of this type */
private FillPreference getFillPreference() {
return mFillPreference;
}
/** Fully qualified class name of views of this type */
private String getFcqn() {
return mFqcn;
}
private String getDisplayName() {
return mDisplayName;
}
private String getResize() {
return mResize;
}
// Implements Comparable<ViewData> such that views can be sorted naturally
@Override
public int compareTo(ViewData other) {
return mOrdinal - other.mOrdinal;
}
public RenderMode getRenderMode() {
return mRenderMode;
}
public boolean getSkip() {
return mSkip;
}
public List<String> getRelatedTo() {
if (mRelatedTo == null || mRelatedTo.length() == 0) {
return Collections.emptyList();
} else {
List<String> result = new ArrayList<String>();
ViewMetadataRepository repository = ViewMetadataRepository.get();
Map<String, ViewData> classToView = repository.getClassToView();
List<String> fqns = new ArrayList<String>(classToView.keySet());
for (String basename : Splitter.on(',').split(mRelatedTo)) {
boolean found = false;
for (String fqcn : fqns) {
String suffix = '.' + basename;
if (fqcn.endsWith(suffix)) {
result.add(fqcn);
found = true;
break;
}
}
if (basename.equals(VIEW_FRAGMENT) || basename.equals(VIEW_INCLUDE)) {
result.add(basename);
} else {
assert found : basename;
}
}
return result;
}
}
public List<String> getTopAttributes() {
// "id" is a top attribute for all views, so it is not included in the XML, we just
// add it in dynamically here
if (mTopAttrs == null || mTopAttrs.length() == 0) {
return Collections.singletonList(ATTR_ID);
} else {
String[] split = mTopAttrs.split(","); //$NON-NLS-1$
List<String> topAttributes = new ArrayList<String>(split.length + 1);
topAttributes.add(ATTR_ID);
for (int i = 0, n = split.length; i < n; i++) {
topAttributes.add(split[i]);
}
return Collections.<String>unmodifiableList(topAttributes);
}
}
void addVariation(ViewData variation) {
if (mVariations == null) {
mVariations = new ArrayList<ViewData>(4);
}
mVariations.add(variation);
}
List<ViewData> getVariations() {
return mVariations;
}
boolean hasVariations() {
return mVariations != null && mVariations.size() > 0;
}
private void setInitString(String initString) {
this.mInitString = initString;
}
private String getInitString() {
return mInitString;
}
private void setIconName(String iconName) {
this.mIconName = iconName;
}
private String getIconName() {
return mIconName;
}
}
/**
* Returns the {@link FillPreference} for classes with the given fully qualified class
* name
*
* @param fqcn the fully qualified class name of the view
* @return a suitable {@link FillPreference} for the given view type
*/
public FillPreference getFillPreference(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
return view.getFillPreference();
}
return FillPreference.NONE;
}
/**
* Returns the {@link RenderMode} for classes with the given fully qualified class
* name
*
* @param fqcn the fully qualified class name
* @return the {@link RenderMode} to use for previews of the given view type
*/
public RenderMode getRenderMode(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
return view.getRenderMode();
}
return RenderMode.NORMAL;
}
/**
* Returns the {@link ResizePolicy} for the given class.
*
* @param fqcn the fully qualified class name of the target widget
* @return the {@link ResizePolicy} for the widget, which will never be null (but may
* be the default of {@link ResizePolicy#full()} if no metadata is found for
* the given widget)
*/
public ResizePolicy getResizePolicy(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
String resize = view.getResize();
if (resize != null && resize.length() > 0) {
if ("full".equals(resize)) { //$NON-NLS-1$
return ResizePolicy.full();
} else if ("none".equals(resize)) { //$NON-NLS-1$
return ResizePolicy.none();
} else if ("horizontal".equals(resize)) { //$NON-NLS-1$
return ResizePolicy.horizontal();
} else if ("vertical".equals(resize)) { //$NON-NLS-1$
return ResizePolicy.vertical();
} else if ("scaled".equals(resize)) { //$NON-NLS-1$
return ResizePolicy.scaled();
} else {
assert false : resize;
}
}
}
return ResizePolicy.full();
}
/**
* Returns true if classes with the given fully qualified class name should be hidden
* or skipped from the palette
*
* @param fqcn the fully qualified class name
* @return true if views of the given type should be hidden from the palette
*/
public boolean getSkip(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
return view.getSkip();
}
return false;
}
/**
* Returns a list of the top (most commonly set) attributes of the given
* view.
*
* @param fqcn the fully qualified class name
* @return a list, never null but possibly empty, of popular attribute names
* (not including a namespace prefix)
*/
public List<String> getTopAttributes(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
return view.getTopAttributes();
}
return Collections.singletonList(ATTR_ID);
}
/**
* Returns a set of fully qualified names for views that are closely related to the
* given view
*
* @param fqcn the fully qualified class name
* @return a list, never null but possibly empty, of views that are related to the
* view of the given type
*/
public List<String> getRelatedTo(String fqcn) {
ViewData view = getClassToView().get(fqcn);
if (view != null) {
return view.getRelatedTo();
}
return Collections.emptyList();
}
/** Render mode for palette preview */
public enum RenderMode {
/**
* Render previews, and it can be rendered as a sibling of many other views in a
* big linear layout
*/
NORMAL,
/** This view needs to be rendered alone */
ALONE,
/**
* Skip this element; it doesn't work or does not produce any visible artifacts
* (such as the basic layouts)
*/
SKIP;
/**
* Returns the {@link RenderMode} for the given render XML attribute
* value
*
* @param render the attribute value in the metadata XML file
* @return a corresponding {@link RenderMode}, never null
*/
public static RenderMode get(String render) {
if ("alone".equals(render)) { //$NON-NLS-1$
return ALONE;
} else if ("skip".equals(render)) { //$NON-NLS-1$
return SKIP;
} else {
return NORMAL;
}
}
}
/**
* Are insets supported yet? This flag indicates whether the {@link #getInsets} method
* can return valid data, such that clients can avoid doing any work computing the
* current theme or density if there's no chance that valid insets will be returned
*/
public static final boolean INSETS_SUPPORTED = false;
/**
* Returns the insets of widgets with the given fully qualified name, in the given
* theme and the given screen density.
*
* @param fqcn the fully qualified name of the view
* @param density the screen density
* @param theme the theme name
* @return the insets of the visual bounds relative to the view info bounds, or null
* if not known or if there are no insets
*/
public static Margins getInsets(String fqcn, Density density, String theme) {
if (INSETS_SUPPORTED) {
// Some sample data measured manually for common themes and widgets.
if (fqcn.equals(FQCN_BUTTON)) {
if (density == Density.HIGH) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, WVGA
return new Margins(5, 5, 5, 5);
} else {
// Theme.Light, WVGA
return new Margins(4, 4, 0, 7);
}
} else if (density == Density.MEDIUM) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, WVGA
return new Margins(3, 3, 3, 3);
} else {
// Theme.Light, HVGA
return new Margins(2, 2, 0, 4);
}
} else if (density == Density.LOW) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, QVGA
return new Margins(2, 2, 2, 2);
} else {
// Theme.Light, QVGA
return new Margins(1, 3, 0, 4);
}
}
} else if (fqcn.equals(FQCN_TOGGLE_BUTTON)) {
if (density == Density.HIGH) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, WVGA
return new Margins(5, 5, 5, 5);
} else {
// Theme.Light, WVGA
return new Margins(2, 2, 0, 5);
}
} else if (density == Density.MEDIUM) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, WVGA
return new Margins(3, 3, 3, 3);
} else {
// Theme.Light, HVGA
return new Margins(0, 1, 0, 3);
}
} else if (density == Density.LOW) {
if (theme.startsWith(HOLO_PREFIX)) {
// Theme.Holo, Theme.Holo.Light, QVGA
return new Margins(2, 2, 2, 2);
} else {
// Theme.Light, QVGA
return new Margins(2, 2, 0, 4);
}
}
} else if (fqcn.equals(FQCN_SPINNER)) {
if (density == Density.HIGH) {
if (!theme.startsWith(HOLO_PREFIX)) {
// Theme.Light, WVGA
return new Margins(3, 4, 2, 8);
} // Doesn't render on Holo!
} else if (density == Density.MEDIUM) {
if (!theme.startsWith(HOLO_PREFIX)) {
// Theme.Light, HVGA
return new Margins(1, 1, 0, 4);
}
}
}
}
return null;
}
private static final String HOLO_PREFIX = "Theme.Holo"; //$NON-NLS-1$
}