| /* |
| * 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; |
| } |
| } |