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