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