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