• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
ProjectCallback(LayoutLibrary layoutLib, ProjectResources projectRes, IProject project, Object credential)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 
getMissingClasses()114     public Set<String> getMissingClasses() {
115         return mMissingClasses;
116     }
117 
getUninstantiatableClasses()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      */
setLogger(LayoutLog logger)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      */
getLogger()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")
loadView(String className, Class[] constructorSignature, Object[] constructorParameters)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 
getShortClassName(String fqcn)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
getNamespace()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
resolveResourceId(int id)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
resolveResourceId(int[] id)317     public String resolveResourceId(int[] id) {
318         if (mProjectRes != null) {
319             return mProjectRes.resolveStyleable(id);
320         }
321 
322         return null;
323     }
324 
325     @Override
getResourceId(ResourceType type, String name)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      */
isUsed()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")
instantiateClass(Class<?> clazz, Class[] constructorSignature, Object[] constructorParameters)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 
setLayoutParser(String layoutName, ILayoutPullParser layoutParser)441     public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) {
442         mLayoutName = layoutName;
443         mLayoutEmbeddedParser = layoutParser;
444     }
445 
446     @Override
getParser(String layoutName)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
getParser(ResourceValue layoutResource)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 
getParser(String layoutName, File xml)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
getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, ResourceReference itemRef, int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition, ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue)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      */
getListAdapterViewFqcn(Class<?> clz)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      */
isWithinIllegalParent(Object viewObject, int depth)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
getAdapterBinding(final ResourceReference adapterView, final Object adapterCookie, final Object viewObject)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      */
setResourceResolver(ResourceResolver resolver)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.
appendToIdeLog(Throwable exception, String format, Object ... args)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
getActionBarCallback()680     public ActionBarCallback getActionBarCallback() {
681         return new ActionBarCallback();
682     }
683 }
684