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