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      */
ProjectCallback(LayoutLibrary layoutLib, ProjectResources projectRes, IProject project, Object credential, GraphicalEditorPart editor)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 
getMissingClasses()119     public Set<String> getMissingClasses() {
120         return mMissingClasses;
121     }
122 
getUninstantiatableClasses()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      */
setLogger(LayoutLog logger)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      */
getLogger()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")
loadView(String className, Class[] constructorSignature, Object[] constructorParameters)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 
getShortClassName(String fqcn)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
getNamespace()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
resolveResourceId(int id)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
resolveResourceId(int[] id)322     public String resolveResourceId(int[] id) {
323         if (mProjectRes != null) {
324             return mProjectRes.resolveStyleable(id);
325         }
326 
327         return null;
328     }
329 
330     @Override
getResourceId(ResourceType type, String name)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      */
isUsed()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")
instantiateClass(Class<?> clazz, Class[] constructorSignature, Object[] constructorParameters)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 
setLayoutParser(String layoutName, ILayoutPullParser layoutParser)446     public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) {
447         mLayoutName = layoutName;
448         mLayoutEmbeddedParser = layoutParser;
449     }
450 
451     @Override
getParser(String layoutName)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
getParser(ResourceValue layoutResource)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 
getParser(String layoutName, File xml)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
getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, ResourceReference itemRef, int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition, ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue)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      */
getListAdapterViewFqcn(Class<?> clz)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      */
isWithinIllegalParent(Object viewObject, int depth)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
getAdapterBinding(final ResourceReference adapterView, final Object adapterCookie, final Object viewObject)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      */
setResourceResolver(ResourceResolver resolver)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.
appendToIdeLog(Throwable exception, String format, Object ... args)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
getActionBarCallback()685     public ActionBarCallback getActionBarCallback() {
686         return new ActionBarHandler(mEditor);
687     }
688 
689     @Override
supports(int feature)690     public boolean supports(int feature) {
691         return feature <= Features.LAST_CAPABILITY;
692     }
693 }
694