aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
blob: 020c666b9cb45935dabfd5acebd21702a8b9e529 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.editors.layout;

import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
import static com.android.SdkConstants.CALENDAR_VIEW;
import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
import static com.android.SdkConstants.FQCN_GRID_VIEW;
import static com.android.SdkConstants.FQCN_SPINNER;
import static com.android.SdkConstants.GRID_VIEW;
import static com.android.SdkConstants.LIST_VIEW;
import static com.android.SdkConstants.SPINNER;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;

import com.android.SdkConstants;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.RenderSecurityManager;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.DataBindingItem;
import com.android.ide.common.rendering.api.Features;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.xml.ManifestData;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderLogger;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectClassLoader;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.resources.ResourceType;
import com.android.util.Pair;
import com.google.common.base.Charsets;
import com.google.common.io.Files;

import org.eclipse.core.resources.IProject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * Loader for Android Project class in order to use them in the layout editor.
 * <p/>This implements {@link IProjectCallback} for the old and new API through
 * {@link LayoutlibCallback}
 */
public final class ProjectCallback extends LayoutlibCallback {
    private final HashMap<String, Class<?>> mLoadedClasses = new HashMap<String, Class<?>>();
    private final Set<String> mMissingClasses = new TreeSet<String>();
    private final Set<String> mBrokenClasses = new TreeSet<String>();
    private final IProject mProject;
    private final ClassLoader mParentClassLoader;
    private final ProjectResources mProjectRes;
    private final Object mCredential;
    private boolean mUsed = false;
    private String mNamespace;
    private ProjectClassLoader mLoader = null;
    private LayoutLog mLogger;
    private LayoutLibrary mLayoutLib;
    private String mLayoutName;
    private ILayoutPullParser mLayoutEmbeddedParser;
    private ResourceResolver mResourceResolver;
    private GraphicalEditorPart mEditor;

    /**
     * Creates a new {@link ProjectCallback} to be used with the layout lib.
     *
     * @param layoutLib The layout library this callback is going to be invoked from
     * @param projectRes the {@link ProjectResources} for the project.
     * @param project the project.
     * @param credential the sandbox credential
     */
    public ProjectCallback(LayoutLibrary layoutLib,
            ProjectResources projectRes, IProject project, Object credential,
            GraphicalEditorPart editor) {
        mLayoutLib = layoutLib;
        mParentClassLoader = layoutLib.getClassLoader();
        mProjectRes = projectRes;
        mProject = project;
        mCredential = credential;
        mEditor = editor;
    }

    public Set<String> getMissingClasses() {
        return mMissingClasses;
    }

    public Set<String> getUninstantiatableClasses() {
        return mBrokenClasses;
    }

    /**
     * Sets the {@link LayoutLog} logger to use for error messages during problems
     *
     * @param logger the new logger to use, or null to clear it out
     */
    public void setLogger(LayoutLog logger) {
        mLogger = logger;
    }

    /**
     * Returns the {@link LayoutLog} logger used for error messages, or null
     *
     * @return the logger being used, or null if no logger is in use
     */
    public LayoutLog getLogger() {
        return mLogger;
    }

    /**
     * {@inheritDoc}
     *
     * This implementation goes through the output directory of the Eclipse project and loads the
     * <code>.class</code> file directly.
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object loadView(String className, Class[] constructorSignature,
            Object[] constructorParameters)
            throws Exception {
        mUsed = true;

        if (className == null) {
            // Just make a plain <View> if you specify <view> without a class= attribute.
            className = CLASS_VIEW;
        }

        // look for a cached version
        Class<?> clazz = mLoadedClasses.get(className);
        if (clazz != null) {
            return instantiateClass(clazz, constructorSignature, constructorParameters);
        }

        // load the class.

        try {
            if (mLoader == null) {
                // Allow creating class loaders during rendering; may be prevented by the
                // RenderSecurityManager
                boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
                try {
                  mLoader = new ProjectClassLoader(mParentClassLoader, mProject);
                } finally {
                    RenderSecurityManager.exitSafeRegion(token);
                }
            }
            clazz = mLoader.loadClass(className);
        } catch (Exception e) {
            // Add the missing class to the list so that the renderer can print them later.
            // no need to log this.
            if (!className.equals(VIEW_FRAGMENT) && !className.equals(VIEW_INCLUDE)) {
                mMissingClasses.add(className);
            }
        }

        try {
            if (clazz != null) {
                // first try to instantiate it because adding it the list of loaded class so that
                // we don't add broken classes.
                Object view = instantiateClass(clazz, constructorSignature, constructorParameters);
                mLoadedClasses.put(className, clazz);

                return view;
            }
        } catch (Throwable e) {
            // Find root cause to log it.
            while (e.getCause() != null) {
                e = e.getCause();
            }

            appendToIdeLog(e, "%1$s failed to instantiate.", className); //$NON-NLS-1$

            // Add the missing class to the list so that the renderer can print them later.
            if (mLogger instanceof RenderLogger) {
                RenderLogger renderLogger = (RenderLogger) mLogger;
                renderLogger.recordThrowable(e);

            }
            mBrokenClasses.add(className);
        }

        // Create a mock view instead. We don't cache it in the mLoadedClasses map.
        // If any exception is thrown, we'll return a CFN with the original class name instead.
        try {
            clazz = mLoader.loadClass(SdkConstants.CLASS_MOCK_VIEW);
            Object view = instantiateClass(clazz, constructorSignature, constructorParameters);

            // Set the text of the mock view to the simplified name of the custom class
            Method m = view.getClass().getMethod("setText",
                                                 new Class<?>[] { CharSequence.class });
            String label = getShortClassName(className);
            if (label.equals(VIEW_FRAGMENT)) {
                label = "<fragment>\n"
                        + "Pick preview layout from the \"Fragment Layout\" context menu";
            } else if (label.equals(VIEW_INCLUDE)) {
                label = "Text";
            }

            m.invoke(view, label);

            // Call MockView.setGravity(Gravity.CENTER) to get the text centered in
            // MockViews.
            // TODO: Do this in layoutlib's MockView class instead.
            try {
                // Look up android.view.Gravity#CENTER - or can we just hard-code
                // the value (17) here?
                Class<?> gravity =
                    Class.forName("android.view.Gravity", //$NON-NLS-1$
                            true, view.getClass().getClassLoader());
                Field centerField = gravity.getField("CENTER"); //$NON-NLS-1$
                int center = centerField.getInt(null);
                m = view.getClass().getMethod("setGravity",
                        new Class<?>[] { Integer.TYPE });
                // Center
                //int center = (0x0001 << 4) | (0x0001 << 0);
                m.invoke(view, Integer.valueOf(center));
            } catch (Exception e) {
                // Not important to center views
            }

            return view;
        } catch (Exception e) {
            // We failed to create and return a mock view.
            // Just throw back a CNF with the original class name.
            throw new ClassNotFoundException(className, e);
        }
    }

    private String getShortClassName(String fqcn) {
        // The name is typically a fully-qualified class name. Let's make it a tad shorter.

        if (fqcn.startsWith("android.")) {                                      //$NON-NLS-1$
            // For android classes, convert android.foo.Name to android...Name
            int first = fqcn.indexOf('.');
            int last = fqcn.lastIndexOf('.');
            if (last > first) {
                return fqcn.substring(0, first) + ".." + fqcn.substring(last);   //$NON-NLS-1$
            }
        } else {
            // For custom non-android classes, it's best to keep the 2 first segments of
            // the namespace, e.g. we want to get something like com.example...MyClass
            int first = fqcn.indexOf('.');
            first = fqcn.indexOf('.', first + 1);
            int last = fqcn.lastIndexOf('.');
            if (last > first) {
                return fqcn.substring(0, first) + ".." + fqcn.substring(last);   //$NON-NLS-1$
            }
        }

        return fqcn;
    }

    /**
     * Returns the namespace for the project. The namespace contains a standard part + the
     * application package.
     *
     * @return The package namespace of the project or null in case of error.
     */
    @Override
    public String getNamespace() {
        if (mNamespace == null) {
            boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
            try {
                ManifestData manifestData = AndroidManifestHelper.parseForData(mProject);
                if (manifestData != null) {
                    String javaPackage = manifestData.getPackage();
                    mNamespace = String.format(AdtConstants.NS_CUSTOM_RESOURCES, javaPackage);
                }
            } finally {
                RenderSecurityManager.exitSafeRegion(token);
            }
        }

        return mNamespace;
    }

    @Override
    public Pair<ResourceType, String> resolveResourceId(int id) {
        if (mProjectRes != null) {
            return mProjectRes.resolveResourceId(id);
        }

        return null;
    }

    @Override
    public String resolveResourceId(int[] id) {
        if (mProjectRes != null) {
            return mProjectRes.resolveStyleable(id);
        }

        return null;
    }

    @Override
    public Integer getResourceId(ResourceType type, String name) {
        if (mProjectRes != null) {
            return mProjectRes.getResourceId(type, name);
        }

        return null;
    }

    /**
     * Returns whether the loader has received requests to load custom views. Note that
     * the custom view loading may not actually have succeeded; this flag only records
     * whether it was <b>requested</b>.
     * <p/>
     * This allows to efficiently only recreate when needed upon code change in the
     * project.
     *
     * @return true if the loader has been asked to load custom views
     */
    public boolean isUsed() {
        return mUsed;
    }

    /**
     * Instantiate a class object, using a specific constructor and parameters.
     * @param clazz the class to instantiate
     * @param constructorSignature the signature of the constructor to use
     * @param constructorParameters the parameters to use in the constructor.
     * @return A new class object, created using a specific constructor and parameters.
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private Object instantiateClass(Class<?> clazz,
            Class[] constructorSignature,
            Object[] constructorParameters) throws Exception {
        Constructor<?> constructor = null;

        try {
            constructor = clazz.getConstructor(constructorSignature);

        } catch (NoSuchMethodException e) {
            // Custom views can either implement a 3-parameter, 2-parameter or a
            // 1-parameter. Let's synthetically build and try all the alternatives.
            // That's kind of like switching to the other box.
            //
            // The 3-parameter constructor takes the following arguments:
            // ...(Context context, AttributeSet attrs, int defStyle)

            int n = constructorSignature.length;
            if (n == 0) {
                // There is no parameter-less constructor. Nobody should ask for one.
                throw e;
            }

            for (int i = 3; i >= 1; i--) {
                if (i == n) {
                    // Let's skip the one we know already fails
                    continue;
                }
                Class[] sig = new Class[i];
                Object[] params = new Object[i];

                int k = i;
                if (n < k) {
                    k = n;
                }
                System.arraycopy(constructorSignature, 0, sig, 0, k);
                System.arraycopy(constructorParameters, 0, params, 0, k);

                for (k++; k <= i; k++) {
                    if (k == 2) {
                        // Parameter 2 is the AttributeSet
                        sig[k-1] = clazz.getClassLoader().loadClass("android.util.AttributeSet");
                        params[k-1] = null;

                    } else if (k == 3) {
                        // Parameter 3 is the int defstyle
                        sig[k-1] = int.class;
                        params[k-1] = 0;
                    }
                }

                constructorSignature = sig;
                constructorParameters = params;

                try {
                    // Try again...
                    constructor = clazz.getConstructor(constructorSignature);
                    if (constructor != null) {
                        // Found a suitable constructor, now let's use it.
                        // (But let's warn the user if the simple View constructor was found
                        // since Unexpected Things may happen if the attribute set constructors
                        // are not found)
                        if (constructorSignature.length < 2 && mLogger != null) {
                            mLogger.warning("wrongconstructor", //$NON-NLS-1$
                                String.format("Custom view %1$s is not using the 2- or 3-argument "
                                    + "View constructors; XML attributes will not work",
                                    clazz.getSimpleName()), null /*data*/);
                        }
                        break;
                    }
                } catch (NoSuchMethodException e1) {
                    // pass
                }
            }

            // If all the alternatives failed, throw the initial exception.
            if (constructor == null) {
                throw e;
            }
        }

        constructor.setAccessible(true);
        return constructor.newInstance(constructorParameters);
    }

    public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) {
        mLayoutName = layoutName;
        mLayoutEmbeddedParser = layoutParser;
    }

    @Override
    public ILayoutPullParser getParser(String layoutName) {
        boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
        try {
            // Try to compute the ResourceValue for this layout since layoutlib
            // must be an older version which doesn't pass the value:
            if (mResourceResolver != null) {
                ResourceValue value = mResourceResolver.getProjectResource(ResourceType.LAYOUT,
                        layoutName);
                if (value != null) {
                    return getParser(value);
                }
            }

            return getParser(layoutName, null);
        } finally {
            RenderSecurityManager.exitSafeRegion(token);
        }
    }

    @Override
    public ILayoutPullParser getParser(ResourceValue layoutResource) {
        boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
        try {
            return getParser(layoutResource.getName(),
                    new File(layoutResource.getValue()));
        } finally {
            RenderSecurityManager.exitSafeRegion(token);
        }
    }

    private ILayoutPullParser getParser(String layoutName, File xml) {
        if (layoutName.equals(mLayoutName)) {
            ILayoutPullParser parser = mLayoutEmbeddedParser;
            // The parser should only be used once!! If it is included more than once,
            // subsequent includes should just use a plain pull parser that is not tied
            // to the XML model
            mLayoutEmbeddedParser = null;
            return parser;
        }

        // For included layouts, create a ContextPullParser such that we get the
        // layout editor behavior in included layouts as well - which for example
        // replaces <fragment> tags with <include>.
        if (xml != null && xml.isFile()) {
            ContextPullParser parser = new ContextPullParser(this, xml);
            try {
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
                String xmlText = Files.toString(xml, Charsets.UTF_8);
                parser.setInput(new StringReader(xmlText));
                return parser;
            } catch (XmlPullParserException e) {
                appendToIdeLog(e, null);
            } catch (FileNotFoundException e) {
                // Shouldn't happen since we check isFile() above
            } catch (IOException e) {
                appendToIdeLog(e, null);
            }
        }

        return null;
    }

    @Override
    public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
            ResourceReference itemRef,
            int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition,
            ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) {

        // Special case for the palette preview
        if (viewAttribute == ViewAttribute.TEXT
                && adapterView.getName().startsWith("android_widget_")) { //$NON-NLS-1$
            String name = adapterView.getName();
            if (viewRef.getName().equals("text2")) { //$NON-NLS-1$
                return "Sub Item";
            }
            if (fullPosition == 0) {
                String viewName = name.substring("android_widget_".length());
                if (viewName.equals(EXPANDABLE_LIST_VIEW)) {
                    return "ExpandableList"; // ExpandableListView is too wide, character-wraps
                }
                return viewName;
            } else {
                return "Next Item";
            }
        }

        if (itemRef.isFramework()) {
            // Special case for list_view_item_2 and friends
            if (viewRef.getName().equals("text2")) { //$NON-NLS-1$
                return "Sub Item " + (fullPosition + 1);
            }
        }

        if (viewAttribute == ViewAttribute.TEXT && ((String) defaultValue).length() == 0) {
            return "Item " + (fullPosition + 1);
        }

        return null;
    }

    /**
     * For the given class, finds and returns the nearest super class which is a ListView
     * or an ExpandableListView or a GridView (which uses a list adapter), or returns null.
     *
     * @param clz the class of the view object
     * @return the fully qualified class name of the list ancestor, or null if there
     *         is no list view ancestor
     */
    public static String getListAdapterViewFqcn(Class<?> clz) {
        String fqcn = clz.getName();
        if (fqcn.endsWith(LIST_VIEW)) { // including EXPANDABLE_LIST_VIEW
            return fqcn;
        } else if (fqcn.equals(FQCN_GRID_VIEW)) {
            return fqcn;
        } else if (fqcn.equals(FQCN_SPINNER)) {
            return fqcn;
        } else if (fqcn.startsWith(ANDROID_PKG_PREFIX)) {
            return null;
        }
        Class<?> superClass = clz.getSuperclass();
        if (superClass != null) {
            return getListAdapterViewFqcn(superClass);
        } else {
            // Should not happen; we would have encountered android.view.View first,
            // and it should have been covered by the ANDROID_PKG_PREFIX case above.
            return null;
        }
    }

    /**
     * Looks at the parent-chain of the view and if it finds a custom view, or a
     * CalendarView, within the given distance then it returns true. A ListView within a
     * CalendarView should not be assigned a custom list view type because it sets its own
     * and then attempts to cast the layout to its own type which would fail if the normal
     * default list item binding is used.
     */
    private boolean isWithinIllegalParent(Object viewObject, int depth) {
        String fqcn = viewObject.getClass().getName();
        if (fqcn.endsWith(CALENDAR_VIEW) || !fqcn.startsWith(ANDROID_PKG_PREFIX)) {
            return true;
        }

        if (depth > 0) {
            Result result = mLayoutLib.getViewParent(viewObject);
            if (result.isSuccess()) {
                Object parent = result.getData();
                if (parent != null) {
                    return isWithinIllegalParent(parent, depth -1);
                }
            }
        }

        return false;
    }

    @Override
    public AdapterBinding getAdapterBinding(final ResourceReference adapterView,
            final Object adapterCookie, final Object viewObject) {
        // Look for user-recorded preference for layout to be used for previews
        if (adapterCookie instanceof UiViewElementNode) {
            UiViewElementNode uiNode = (UiViewElementNode) adapterCookie;
            AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, uiNode);
            if (binding != null) {
                return binding;
            }
        } else if (adapterCookie instanceof Map<?,?>) {
            @SuppressWarnings("unchecked")
            Map<String, String> map = (Map<String, String>) adapterCookie;
            AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, map);
            if (binding != null) {
                return binding;
            }
        }

        if (viewObject == null) {
            return null;
        }

        // Is this a ListView or ExpandableListView? If so, return its fully qualified
        // class name, otherwise return null. This is used to filter out other types
        // of AdapterViews (such as Spinners) where we don't want to use the list item
        // binding.
        String listFqcn = getListAdapterViewFqcn(viewObject.getClass());
        if (listFqcn == null) {
            return null;
        }

        // Is this ListView nested within an "illegal" container, such as a CalendarView?
        // If so, don't change the bindings below. Some views, such as CalendarView, and
        // potentially some custom views, might be doing specific things with the ListView
        // that could break if we add our own list binding, so for these leave the list
        // alone.
        if (isWithinIllegalParent(viewObject, 2)) {
            return null;
        }

        int count = listFqcn.endsWith(GRID_VIEW) ? 24 : 12;
        AdapterBinding binding = new AdapterBinding(count);
        if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) {
            binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_EXPANDABLE_LIST_ITEM,
                    true /* isFramework */, 1));
        } else if (listFqcn.equals(SPINNER)) {
            binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_SPINNER_ITEM,
                    true /* isFramework */, 1));
        } else {
            binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_LIST_ITEM,
                    true /* isFramework */, 1));
        }

        return binding;
    }

    /**
     * Sets the {@link ResourceResolver} to be used when looking up resources
     *
     * @param resolver the resolver to use
     */
    public void setResourceResolver(ResourceResolver resolver) {
        mResourceResolver = resolver;
    }

    // Append the given message to the ADT log. Bypass the sandbox if necessary
    // such that we can write to the log file.
    private void appendToIdeLog(Throwable exception, String format, Object ... args) {
        boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
        try {
            AdtPlugin.log(exception, format, args);
        } finally {
            RenderSecurityManager.exitSafeRegion(token);
        }
    }

    @Override
    public ActionBarCallback getActionBarCallback() {
        return new ActionBarHandler(mEditor);
    }

    @Override
    public boolean supports(int feature) {
        return feature <= Features.LAST_CAPABILITY;
    }
}