Home | History | Annotate | Download | only in layout
      1 /*
      2  * Copyright (C) 2008 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;
     18 
     19 import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
     20 import static com.android.SdkConstants.CALENDAR_VIEW;
     21 import static com.android.SdkConstants.CLASS_VIEW;
     22 import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
     23 import static com.android.SdkConstants.FQCN_GRID_VIEW;
     24 import static com.android.SdkConstants.FQCN_SPINNER;
     25 import static com.android.SdkConstants.GRID_VIEW;
     26 import static com.android.SdkConstants.LIST_VIEW;
     27 import static com.android.SdkConstants.SPINNER;
     28 import static com.android.SdkConstants.VIEW_FRAGMENT;
     29 import static com.android.SdkConstants.VIEW_INCLUDE;
     30 
     31 import com.android.SdkConstants;
     32 import com.android.ide.common.rendering.LayoutLibrary;
     33 import com.android.ide.common.rendering.api.AdapterBinding;
     34 import com.android.ide.common.rendering.api.DataBindingItem;
     35 import com.android.ide.common.rendering.api.ILayoutPullParser;
     36 import com.android.ide.common.rendering.api.IProjectCallback;
     37 import com.android.ide.common.rendering.api.LayoutLog;
     38 import com.android.ide.common.rendering.api.ResourceReference;
     39 import com.android.ide.common.rendering.api.ResourceValue;
     40 import com.android.ide.common.rendering.api.Result;
     41 import com.android.ide.common.rendering.legacy.LegacyCallback;
     42 import com.android.ide.common.resources.ResourceResolver;
     43 import com.android.ide.common.xml.ManifestData;
     44 import com.android.ide.eclipse.adt.AdtConstants;
     45 import com.android.ide.eclipse.adt.AdtPlugin;
     46 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata;
     47 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderLogger;
     48 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     49 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     50 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectClassLoader;
     51 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     52 import com.android.resources.ResourceType;
     53 import com.android.util.Pair;
     54 import com.google.common.base.Charsets;
     55 import com.google.common.io.Files;
     56 
     57 import org.eclipse.core.resources.IProject;
     58 import org.xmlpull.v1.XmlPullParser;
     59 import org.xmlpull.v1.XmlPullParserException;
     60 
     61 import java.io.File;
     62 import java.io.FileNotFoundException;
     63 import java.io.IOException;
     64 import java.io.StringReader;
     65 import java.lang.reflect.Constructor;
     66 import java.lang.reflect.Field;
     67 import java.lang.reflect.Method;
     68 import java.util.HashMap;
     69 import java.util.Map;
     70 import java.util.Set;
     71 import java.util.TreeSet;
     72 
     73 /**
     74  * Loader for Android Project class in order to use them in the layout editor.
     75  * <p/>This implements {@link IProjectCallback} for the old and new API through
     76  * {@link LegacyCallback}
     77  */
     78 public final class ProjectCallback extends LegacyCallback {
     79     private final HashMap<String, Class<?>> mLoadedClasses = new HashMap<String, Class<?>>();
     80     private final Set<String> mMissingClasses = new TreeSet<String>();
     81     private final Set<String> mBrokenClasses = new TreeSet<String>();
     82     private final IProject mProject;
     83     private final ClassLoader mParentClassLoader;
     84     private final ProjectResources mProjectRes;
     85     private boolean mUsed = false;
     86     private String mNamespace;
     87     private ProjectClassLoader mLoader = null;
     88     private LayoutLog mLogger;
     89     private LayoutLibrary mLayoutLib;
     90 
     91     private String mLayoutName;
     92     private ILayoutPullParser mLayoutEmbeddedParser;
     93     private ResourceResolver mResourceResolver;
     94 
     95     /**
     96      * Creates a new {@link ProjectCallback} to be used with the layout lib.
     97      *
     98      * @param layoutLib The layout library this callback is going to be invoked from
     99      * @param projectRes the {@link ProjectResources} for the project.
    100      * @param project the project.
    101      */
    102     public ProjectCallback(LayoutLibrary layoutLib,
    103             ProjectResources projectRes, IProject project) {
    104         mLayoutLib = layoutLib;
    105         mParentClassLoader = layoutLib.getClassLoader();
    106         mProjectRes = projectRes;
    107         mProject = project;
    108     }
    109 
    110     public Set<String> getMissingClasses() {
    111         return mMissingClasses;
    112     }
    113 
    114     public Set<String> getUninstantiatableClasses() {
    115         return mBrokenClasses;
    116     }
    117 
    118     /**
    119      * Sets the {@link LayoutLog} logger to use for error messages during problems
    120      *
    121      * @param logger the new logger to use, or null to clear it out
    122      */
    123     public void setLogger(LayoutLog logger) {
    124         mLogger = logger;
    125     }
    126 
    127     /**
    128      * Returns the {@link LayoutLog} logger used for error messages, or null
    129      *
    130      * @return the logger being used, or null if no logger is in use
    131      */
    132     public LayoutLog getLogger() {
    133         return mLogger;
    134     }
    135 
    136     /**
    137      * {@inheritDoc}
    138      *
    139      * This implementation goes through the output directory of the Eclipse project and loads the
    140      * <code>.class</code> file directly.
    141      */
    142     @Override
    143     @SuppressWarnings("unchecked")
    144     public Object loadView(String className, Class[] constructorSignature,
    145             Object[] constructorParameters)
    146             throws ClassNotFoundException, Exception {
    147         mUsed = true;
    148 
    149         if (className == null) {
    150             // Just make a plain <View> if you specify <view> without a class= attribute.
    151             className = CLASS_VIEW;
    152         }
    153 
    154         // look for a cached version
    155         Class<?> clazz = mLoadedClasses.get(className);
    156         if (clazz != null) {
    157             return instantiateClass(clazz, constructorSignature, constructorParameters);
    158         }
    159 
    160         // load the class.
    161 
    162         try {
    163             if (mLoader == null) {
    164                 mLoader = new ProjectClassLoader(mParentClassLoader, mProject);
    165             }
    166             clazz = mLoader.loadClass(className);
    167         } catch (Exception e) {
    168             // Add the missing class to the list so that the renderer can print them later.
    169             // no need to log this.
    170             if (!className.equals(VIEW_FRAGMENT) && !className.equals(VIEW_INCLUDE)) {
    171                 mMissingClasses.add(className);
    172             }
    173         }
    174 
    175         try {
    176             if (clazz != null) {
    177                 // first try to instantiate it because adding it the list of loaded class so that
    178                 // we don't add broken classes.
    179                 Object view = instantiateClass(clazz, constructorSignature, constructorParameters);
    180                 mLoadedClasses.put(className, clazz);
    181 
    182                 return view;
    183             }
    184         } catch (Throwable e) {
    185             // Find root cause to log it.
    186             while (e.getCause() != null) {
    187                 e = e.getCause();
    188             }
    189 
    190             AdtPlugin.log(e, "%1$s failed to instantiate.", className); //$NON-NLS-1$
    191 
    192             // Add the missing class to the list so that the renderer can print them later.
    193             if (mLogger instanceof RenderLogger) {
    194                 RenderLogger renderLogger = (RenderLogger) mLogger;
    195                 renderLogger.recordThrowable(e);
    196 
    197             }
    198             mBrokenClasses.add(className);
    199         }
    200 
    201         // Create a mock view instead. We don't cache it in the mLoadedClasses map.
    202         // If any exception is thrown, we'll return a CFN with the original class name instead.
    203         try {
    204             clazz = mLoader.loadClass(SdkConstants.CLASS_MOCK_VIEW);
    205             Object view = instantiateClass(clazz, constructorSignature, constructorParameters);
    206 
    207             // Set the text of the mock view to the simplified name of the custom class
    208             Method m = view.getClass().getMethod("setText",
    209                                                  new Class<?>[] { CharSequence.class });
    210             String label = getShortClassName(className);
    211             if (label.equals(VIEW_FRAGMENT)) {
    212                 label = "<fragment>\n"
    213                         + "Pick preview layout from the \"Fragment Layout\" context menu";
    214             } else if (label.equals(VIEW_INCLUDE)) {
    215                 label = "Text";
    216             }
    217 
    218             m.invoke(view, label);
    219 
    220             // Call MockView.setGravity(Gravity.CENTER) to get the text centered in
    221             // MockViews.
    222             // TODO: Do this in layoutlib's MockView class instead.
    223             try {
    224                 // Look up android.view.Gravity#CENTER - or can we just hard-code
    225                 // the value (17) here?
    226                 Class<?> gravity =
    227                     Class.forName("android.view.Gravity", //$NON-NLS-1$
    228                             true, view.getClass().getClassLoader());
    229                 Field centerField = gravity.getField("CENTER"); //$NON-NLS-1$
    230                 int center = centerField.getInt(null);
    231                 m = view.getClass().getMethod("setGravity",
    232                         new Class<?>[] { Integer.TYPE });
    233                 // Center
    234                 //int center = (0x0001 << 4) | (0x0001 << 0);
    235                 m.invoke(view, Integer.valueOf(center));
    236             } catch (Exception e) {
    237                 // Not important to center views
    238             }
    239 
    240             return view;
    241         } catch (Exception e) {
    242             // We failed to create and return a mock view.
    243             // Just throw back a CNF with the original class name.
    244             throw new ClassNotFoundException(className, e);
    245         }
    246     }
    247 
    248     private String getShortClassName(String fqcn) {
    249         // The name is typically a fully-qualified class name. Let's make it a tad shorter.
    250 
    251         if (fqcn.startsWith("android.")) {                                      //$NON-NLS-1$
    252             // For android classes, convert android.foo.Name to android...Name
    253             int first = fqcn.indexOf('.');
    254             int last = fqcn.lastIndexOf('.');
    255             if (last > first) {
    256                 return fqcn.substring(0, first) + ".." + fqcn.substring(last);   //$NON-NLS-1$
    257             }
    258         } else {
    259             // For custom non-android classes, it's best to keep the 2 first segments of
    260             // the namespace, e.g. we want to get something like com.example...MyClass
    261             int first = fqcn.indexOf('.');
    262             first = fqcn.indexOf('.', first + 1);
    263             int last = fqcn.lastIndexOf('.');
    264             if (last > first) {
    265                 return fqcn.substring(0, first) + ".." + fqcn.substring(last);   //$NON-NLS-1$
    266             }
    267         }
    268 
    269         return fqcn;
    270     }
    271 
    272     /**
    273      * Returns the namespace for the project. The namespace contains a standard part + the
    274      * application package.
    275      *
    276      * @return The package namespace of the project or null in case of error.
    277      */
    278     @Override
    279     public String getNamespace() {
    280         if (mNamespace == null) {
    281             ManifestData manifestData = AndroidManifestHelper.parseForData(mProject);
    282             if (manifestData != null) {
    283                 String javaPackage = manifestData.getPackage();
    284                 mNamespace = String.format(AdtConstants.NS_CUSTOM_RESOURCES, javaPackage);
    285             }
    286         }
    287 
    288         return mNamespace;
    289     }
    290 
    291     @Override
    292     public Pair<ResourceType, String> resolveResourceId(int id) {
    293         if (mProjectRes != null) {
    294             return mProjectRes.resolveResourceId(id);
    295         }
    296 
    297         return null;
    298     }
    299 
    300     @Override
    301     public String resolveResourceId(int[] id) {
    302         if (mProjectRes != null) {
    303             return mProjectRes.resolveStyleable(id);
    304         }
    305 
    306         return null;
    307     }
    308 
    309     @Override
    310     public Integer getResourceId(ResourceType type, String name) {
    311         if (mProjectRes != null) {
    312             return mProjectRes.getResourceId(type, name);
    313         }
    314 
    315         return null;
    316     }
    317 
    318     /**
    319      * Returns whether the loader has received requests to load custom views. Note that
    320      * the custom view loading may not actually have succeeded; this flag only records
    321      * whether it was <b>requested</b>.
    322      * <p/>
    323      * This allows to efficiently only recreate when needed upon code change in the
    324      * project.
    325      *
    326      * @return true if the loader has been asked to load custom views
    327      */
    328     public boolean isUsed() {
    329         return mUsed;
    330     }
    331 
    332     /**
    333      * Instantiate a class object, using a specific constructor and parameters.
    334      * @param clazz the class to instantiate
    335      * @param constructorSignature the signature of the constructor to use
    336      * @param constructorParameters the parameters to use in the constructor.
    337      * @return A new class object, created using a specific constructor and parameters.
    338      * @throws Exception
    339      */
    340     @SuppressWarnings("unchecked")
    341     private Object instantiateClass(Class<?> clazz,
    342             Class[] constructorSignature,
    343             Object[] constructorParameters) throws Exception {
    344         Constructor<?> constructor = null;
    345 
    346         try {
    347             constructor = clazz.getConstructor(constructorSignature);
    348 
    349         } catch (NoSuchMethodException e) {
    350             // Custom views can either implement a 3-parameter, 2-parameter or a
    351             // 1-parameter. Let's synthetically build and try all the alternatives.
    352             // That's kind of like switching to the other box.
    353             //
    354             // The 3-parameter constructor takes the following arguments:
    355             // ...(Context context, AttributeSet attrs, int defStyle)
    356 
    357             int n = constructorSignature.length;
    358             if (n == 0) {
    359                 // There is no parameter-less constructor. Nobody should ask for one.
    360                 throw e;
    361             }
    362 
    363             for (int i = 3; i >= 1; i--) {
    364                 if (i == n) {
    365                     // Let's skip the one we know already fails
    366                     continue;
    367                 }
    368                 Class[] sig = new Class[i];
    369                 Object[] params = new Object[i];
    370 
    371                 int k = i;
    372                 if (n < k) {
    373                     k = n;
    374                 }
    375                 System.arraycopy(constructorSignature, 0, sig, 0, k);
    376                 System.arraycopy(constructorParameters, 0, params, 0, k);
    377 
    378                 for (k++; k <= i; k++) {
    379                     if (k == 2) {
    380                         // Parameter 2 is the AttributeSet
    381                         sig[k-1] = clazz.getClassLoader().loadClass("android.util.AttributeSet");
    382                         params[k-1] = null;
    383 
    384                     } else if (k == 3) {
    385                         // Parameter 3 is the int defstyle
    386                         sig[k-1] = int.class;
    387                         params[k-1] = 0;
    388                     }
    389                 }
    390 
    391                 constructorSignature = sig;
    392                 constructorParameters = params;
    393 
    394                 try {
    395                     // Try again...
    396                     constructor = clazz.getConstructor(constructorSignature);
    397                     if (constructor != null) {
    398                         // Found a suitable constructor, now let's use it.
    399                         // (But let's warn the user if the simple View constructor was found
    400                         // since Unexpected Things may happen if the attribute set constructors
    401                         // are not found)
    402                         if (constructorSignature.length < 2 && mLogger != null) {
    403                             mLogger.warning("wrongconstructor", //$NON-NLS-1$
    404                                 String.format("Custom view %1$s is not using the 2- or 3-argument "
    405                                     + "View constructors; XML attributes will not work",
    406                                     clazz.getSimpleName()), null /*data*/);
    407                         }
    408                         break;
    409                     }
    410                 } catch (NoSuchMethodException e1) {
    411                     // pass
    412                 }
    413             }
    414 
    415             // If all the alternatives failed, throw the initial exception.
    416             if (constructor == null) {
    417                 throw e;
    418             }
    419         }
    420 
    421         constructor.setAccessible(true);
    422         return constructor.newInstance(constructorParameters);
    423     }
    424 
    425     public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) {
    426         mLayoutName = layoutName;
    427         mLayoutEmbeddedParser = layoutParser;
    428     }
    429 
    430     @Override
    431     public ILayoutPullParser getParser(String layoutName) {
    432         // Try to compute the ResourceValue for this layout since layoutlib
    433         // must be an older version which doesn't pass the value:
    434         if (mResourceResolver != null) {
    435             ResourceValue value = mResourceResolver.getProjectResource(ResourceType.LAYOUT,
    436                     layoutName);
    437             if (value != null) {
    438                 return getParser(value);
    439             }
    440         }
    441 
    442         return getParser(layoutName, null);
    443     }
    444 
    445     @Override
    446     public ILayoutPullParser getParser(ResourceValue layoutResource) {
    447         return getParser(layoutResource.getName(),
    448                 new File(layoutResource.getValue()));
    449     }
    450 
    451     private ILayoutPullParser getParser(String layoutName, File xml) {
    452         if (layoutName.equals(mLayoutName)) {
    453             ILayoutPullParser parser = mLayoutEmbeddedParser;
    454             // The parser should only be used once!! If it is included more than once,
    455             // subsequent includes should just use a plain pull parser that is not tied
    456             // to the XML model
    457             mLayoutEmbeddedParser = null;
    458             return parser;
    459         }
    460 
    461         // For included layouts, create a ContextPullParser such that we get the
    462         // layout editor behavior in included layouts as well - which for example
    463         // replaces <fragment> tags with <include>.
    464         if (xml != null && xml.isFile()) {
    465             ContextPullParser parser = new ContextPullParser(this, xml);
    466             try {
    467                 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
    468                 String xmlText = Files.toString(xml, Charsets.UTF_8);
    469                 parser.setInput(new StringReader(xmlText));
    470                 return parser;
    471             } catch (XmlPullParserException e) {
    472                 AdtPlugin.log(e, null);
    473             } catch (FileNotFoundException e) {
    474                 // Shouldn't happen since we check isFile() above
    475             } catch (IOException e) {
    476                 AdtPlugin.log(e, null);
    477             }
    478         }
    479 
    480         return null;
    481     }
    482 
    483     @Override
    484     public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
    485             ResourceReference itemRef,
    486             int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition,
    487             ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) {
    488 
    489         // Special case for the palette preview
    490         if (viewAttribute == ViewAttribute.TEXT
    491                 && adapterView.getName().startsWith("android_widget_")) { //$NON-NLS-1$
    492             String name = adapterView.getName();
    493             if (viewRef.getName().equals("text2")) { //$NON-NLS-1$
    494                 return "Sub Item";
    495             }
    496             if (fullPosition == 0) {
    497                 String viewName = name.substring("android_widget_".length());
    498                 if (viewName.equals(EXPANDABLE_LIST_VIEW)) {
    499                     return "ExpandableList"; // ExpandableListView is too wide, character-wraps
    500                 }
    501                 return viewName;
    502             } else {
    503                 return "Next Item";
    504             }
    505         }
    506 
    507         if (itemRef.isFramework()) {
    508             // Special case for list_view_item_2 and friends
    509             if (viewRef.getName().equals("text2")) { //$NON-NLS-1$
    510                 return "Sub Item " + (fullPosition + 1);
    511             }
    512         }
    513 
    514         if (viewAttribute == ViewAttribute.TEXT && ((String) defaultValue).length() == 0) {
    515             return "Item " + (fullPosition + 1);
    516         }
    517 
    518         return null;
    519     }
    520 
    521     /**
    522      * For the given class, finds and returns the nearest super class which is a ListView
    523      * or an ExpandableListView or a GridView (which uses a list adapter), or returns null.
    524      *
    525      * @param clz the class of the view object
    526      * @return the fully qualified class name of the list ancestor, or null if there
    527      *         is no list view ancestor
    528      */
    529     public static String getListAdapterViewFqcn(Class<?> clz) {
    530         String fqcn = clz.getName();
    531         if (fqcn.endsWith(LIST_VIEW)) { // including EXPANDABLE_LIST_VIEW
    532             return fqcn;
    533         } else if (fqcn.equals(FQCN_GRID_VIEW)) {
    534             return fqcn;
    535         } else if (fqcn.equals(FQCN_SPINNER)) {
    536             return fqcn;
    537         } else if (fqcn.startsWith(ANDROID_PKG_PREFIX)) {
    538             return null;
    539         }
    540         Class<?> superClass = clz.getSuperclass();
    541         if (superClass != null) {
    542             return getListAdapterViewFqcn(superClass);
    543         } else {
    544             // Should not happen; we would have encountered android.view.View first,
    545             // and it should have been covered by the ANDROID_PKG_PREFIX case above.
    546             return null;
    547         }
    548     }
    549 
    550     /**
    551      * Looks at the parent-chain of the view and if it finds a custom view, or a
    552      * CalendarView, within the given distance then it returns true. A ListView within a
    553      * CalendarView should not be assigned a custom list view type because it sets its own
    554      * and then attempts to cast the layout to its own type which would fail if the normal
    555      * default list item binding is used.
    556      */
    557     private boolean isWithinIllegalParent(Object viewObject, int depth) {
    558         String fqcn = viewObject.getClass().getName();
    559         if (fqcn.endsWith(CALENDAR_VIEW) || !fqcn.startsWith(ANDROID_PKG_PREFIX)) {
    560             return true;
    561         }
    562 
    563         if (depth > 0) {
    564             Result result = mLayoutLib.getViewParent(viewObject);
    565             if (result.isSuccess()) {
    566                 Object parent = result.getData();
    567                 if (parent != null) {
    568                     return isWithinIllegalParent(parent, depth -1);
    569                 }
    570             }
    571         }
    572 
    573         return false;
    574     }
    575 
    576     @Override
    577     public AdapterBinding getAdapterBinding(final ResourceReference adapterView,
    578             final Object adapterCookie, final Object viewObject) {
    579         // Look for user-recorded preference for layout to be used for previews
    580         if (adapterCookie instanceof UiViewElementNode) {
    581             UiViewElementNode uiNode = (UiViewElementNode) adapterCookie;
    582             AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, uiNode);
    583             if (binding != null) {
    584                 return binding;
    585             }
    586         } else if (adapterCookie instanceof Map<?,?>) {
    587             @SuppressWarnings("unchecked")
    588             Map<String, String> map = (Map<String, String>) adapterCookie;
    589             AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, map);
    590             if (binding != null) {
    591                 return binding;
    592             }
    593         }
    594 
    595         if (viewObject == null) {
    596             return null;
    597         }
    598 
    599         // Is this a ListView or ExpandableListView? If so, return its fully qualified
    600         // class name, otherwise return null. This is used to filter out other types
    601         // of AdapterViews (such as Spinners) where we don't want to use the list item
    602         // binding.
    603         String listFqcn = getListAdapterViewFqcn(viewObject.getClass());
    604         if (listFqcn == null) {
    605             return null;
    606         }
    607 
    608         // Is this ListView nested within an "illegal" container, such as a CalendarView?
    609         // If so, don't change the bindings below. Some views, such as CalendarView, and
    610         // potentially some custom views, might be doing specific things with the ListView
    611         // that could break if we add our own list binding, so for these leave the list
    612         // alone.
    613         if (isWithinIllegalParent(viewObject, 2)) {
    614             return null;
    615         }
    616 
    617         int count = listFqcn.endsWith(GRID_VIEW) ? 24 : 12;
    618         AdapterBinding binding = new AdapterBinding(count);
    619         if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) {
    620             binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_EXPANDABLE_LIST_ITEM,
    621                     true /* isFramework */, 1));
    622         } else if (listFqcn.equals(SPINNER)) {
    623             binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_SPINNER_ITEM,
    624                     true /* isFramework */, 1));
    625         } else {
    626             binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_LIST_ITEM,
    627                     true /* isFramework */, 1));
    628         }
    629 
    630         return binding;
    631     }
    632 
    633     /**
    634      * Sets the {@link ResourceResolver} to be used when looking up resources
    635      *
    636      * @param resolver the resolver to use
    637      */
    638     public void setResourceResolver(ResourceResolver resolver) {
    639         mResourceResolver = resolver;
    640     }
    641 }
    642