• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
17 
18 import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX;
19 
20 import com.android.ide.common.api.IClientRulesEngine;
21 import com.android.ide.common.api.INode;
22 import com.android.ide.common.api.Rect;
23 import com.android.ide.common.rendering.LayoutLibrary;
24 import com.android.ide.common.rendering.api.DrawableParams;
25 import com.android.ide.common.rendering.api.IImageFactory;
26 import com.android.ide.common.rendering.api.ILayoutPullParser;
27 import com.android.ide.common.rendering.api.LayoutLog;
28 import com.android.ide.common.rendering.api.RenderSession;
29 import com.android.ide.common.rendering.api.ResourceValue;
30 import com.android.ide.common.rendering.api.Result;
31 import com.android.ide.common.rendering.api.SessionParams;
32 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
33 import com.android.ide.common.rendering.api.ViewInfo;
34 import com.android.ide.common.resources.ResourceResolver;
35 import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
36 import com.android.ide.eclipse.adt.AdtPlugin;
37 import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser;
38 import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
39 import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
40 import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
41 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
42 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
43 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
44 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
45 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
46 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
47 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
48 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
49 import com.android.resources.Density;
50 
51 import org.eclipse.core.resources.IProject;
52 import org.xmlpull.v1.XmlPullParser;
53 import org.xmlpull.v1.XmlPullParserException;
54 
55 import java.awt.image.BufferedImage;
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileNotFoundException;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Set;
64 
65 /**
66  * The {@link RenderService} provides rendering and layout information for
67  * Android layouts. This is a wrapper around the layout library.
68  */
69 public class RenderService {
70     /** Reference to the file being edited. Can also be used to access the {@link IProject}. */
71     private final GraphicalEditorPart mEditor;
72 
73     // The following fields are inferred from the editor and not customizable by the
74     // client of the render service:
75 
76     private final IProject mProject;
77     private final ProjectCallback mProjectCallback;
78     private final ResourceResolver mResourceResolver;
79     private final int mMinSdkVersion;
80     private final int mTargetSdkVersion;
81     private final LayoutLibrary mLayoutLib;
82     private final IImageFactory mImageFactory;
83     private final Density mDensity;
84     private final float mXdpi;
85     private final float mYdpi;
86     private final ScreenSizeQualifier mScreenSize;
87 
88     // The following fields are optional or configurable using the various chained
89     // setters:
90 
91     private UiDocumentNode mModel;
92     private int mWidth = -1;
93     private int mHeight = -1;
94     private boolean mUseExplodeMode;
95     private Reference mIncludedWithin;
96     private RenderingMode mRenderingMode = RenderingMode.NORMAL;
97     private LayoutLog mLogger;
98     private Integer mOverrideBgColor;
99     private boolean mShowDecorations = true;
100     private Set<UiElementNode> mExpandNodes = Collections.<UiElementNode>emptySet();
101 
102     /** Use the {@link #create} factory instead */
RenderService(GraphicalEditorPart editor)103     private RenderService(GraphicalEditorPart editor) {
104         mEditor = editor;
105 
106         mProject = editor.getProject();
107         LayoutCanvas canvas = editor.getCanvasControl();
108         mImageFactory = canvas.getImageOverlay();
109         ConfigurationComposite config = editor.getConfigurationComposite();
110         mDensity = config.getDensity();
111         mXdpi = config.getXDpi();
112         mYdpi = config.getYDpi();
113         mScreenSize = config.getCurrentConfig().getScreenSizeQualifier();
114         mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/);
115         mResourceResolver = editor.getResourceResolver();
116         mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib);
117         mMinSdkVersion = editor.getMinSdkVersion();
118         mTargetSdkVersion = editor.getTargetSdkVersion();
119     }
120 
121     /**
122      * Creates a new {@link RenderService} associated with the given editor.
123      *
124      * @param editor the editor to provide configuration data such as the render target
125      * @return a {@link RenderService} which can perform rendering services
126      */
create(GraphicalEditorPart editor)127     public static RenderService create(GraphicalEditorPart editor) {
128         RenderService renderService = new RenderService(editor);
129 
130         return renderService;
131     }
132 
133     /**
134      * Renders the given model, using this editor's theme and screen settings, and returns
135      * the result as a {@link RenderSession}.
136      *
137      * @param model the model to be rendered, which can be different than the editor's own
138      *            {@link #getModel()}.
139      * @param width the width to use for the layout, or -1 to use the width of the screen
140      *            associated with this editor
141      * @param height the height to use for the layout, or -1 to use the height of the screen
142      *            associated with this editor
143      * @param explodeNodes a set of nodes to explode, or null for none
144      * @param overrideBgColor If non-null, use the given color as a background to render over
145      *        rather than the normal background requested by the theme
146      * @param noDecor If true, don't draw window decorations like the system bar
147      * @param logger a logger where rendering errors are reported
148      * @param renderingMode the {@link RenderingMode} to use for rendering
149      * @return the resulting rendered image wrapped in an {@link RenderSession}
150      */
151 
152     /**
153      * Sets the {@link LayoutLog} to be used during rendering. If none is specified, a
154      * silent logger will be used.
155      *
156      * @param logger the log to be used
157      * @return this (such that chains of setters can be stringed together)
158      */
setLog(LayoutLog logger)159     public RenderService setLog(LayoutLog logger) {
160         mLogger = logger;
161         return this;
162     }
163 
164     /**
165      * Sets the model to be rendered, which can be different than the editor's own
166      * {@link GraphicalEditorPart#getModel()}.
167      *
168      * @param model the model to be rendered
169      * @return this (such that chains of setters can be stringed together)
170      */
setModel(UiDocumentNode model)171     public RenderService setModel(UiDocumentNode model) {
172         mModel = model;
173         return this;
174     }
175 
176     /**
177      * Sets the width and height to be used during rendering (which might be adjusted if
178      * the {@link #setRenderingMode(RenderingMode)} is {@link RenderingMode#FULL_EXPAND}.
179      *
180      * @param width the width in pixels of the layout to be rendered
181      * @param height the height in pixels of the layout to be rendered
182      * @return this (such that chains of setters can be stringed together)
183      */
setSize(int width, int height)184     public RenderService setSize(int width, int height) {
185         mWidth = width;
186         mHeight = height;
187         return this;
188     }
189 
190     /**
191      * Sets the {@link RenderingMode} to be used during rendering. If none is specified,
192      * the default is {@link RenderingMode#NORMAL}.
193      *
194      * @param renderingMode the rendering mode to be used
195      * @return this (such that chains of setters can be stringed together)
196      */
setRenderingMode(RenderingMode renderingMode)197     public RenderService setRenderingMode(RenderingMode renderingMode) {
198         mRenderingMode = renderingMode;
199         return this;
200     }
201 
202     /**
203      * Sets the overriding background color to be used, if any. The color should be a
204      * bitmask of AARRGGBB. The default is null.
205      *
206      * @param overrideBgColor the overriding background color to be used in the rendering,
207      *            in the form of a AARRGGBB bitmask, or null to use no custom background.
208      * @return this (such that chains of setters can be stringed together)
209      */
setOverrideBgColor(Integer overrideBgColor)210     public RenderService setOverrideBgColor(Integer overrideBgColor) {
211         mOverrideBgColor = overrideBgColor;
212         return this;
213     }
214 
215     /**
216      * Sets whether the rendering should include decorations such as a system bar, an
217      * application bar etc depending on the SDK target and theme. The default is true.
218      *
219      * @param showDecorations true if the rendering should include system bars etc.
220      * @return this (such that chains of setters can be stringed together)
221      */
setDecorations(boolean showDecorations)222     public RenderService setDecorations(boolean showDecorations) {
223         mShowDecorations = showDecorations;
224         return this;
225     }
226 
227     /**
228      * Sets the nodes to expand during rendering. These will be padded with approximately
229      * 20 pixels and also highlighted by the {@link EmptyViewsOverlay}. The default is an
230      * empty collection.
231      *
232      * @param nodesToExpand the nodes to be expanded
233      * @return this (such that chains of setters can be stringed together)
234      */
setNodesToExpand(Set<UiElementNode> nodesToExpand)235     public RenderService setNodesToExpand(Set<UiElementNode> nodesToExpand) {
236         mExpandNodes = nodesToExpand;
237         return this;
238     }
239 
240     /**
241      * Sets the {@link Reference} to an outer layout that this layout should be rendered
242      * within. The outer layout <b>must</b> contain an include tag which points to this
243      * layout. The default is null.
244      *
245      * @param includedWithin a reference to an outer layout to render this layout within
246      * @return this (such that chains of setters can be stringed together)
247      */
setIncludedWithin(Reference includedWithin)248     public RenderService setIncludedWithin(Reference includedWithin) {
249         mIncludedWithin = includedWithin;
250         return this;
251     }
252 
253     /** Initializes any remaining optional fields after all setters have been called */
finishConfiguration()254     private void finishConfiguration() {
255         if (mLogger == null) {
256             // Silent logging
257             mLogger = new LayoutLog();
258         }
259     }
260 
261     /**
262      * Renders the model and returns the result as a {@link RenderSession}.
263      * @return the {@link RenderSession} resulting from rendering the current model
264      */
createRenderSession()265     public RenderSession createRenderSession() {
266         assert mModel != null && mWidth != -1 && mHeight != -1 : "Incomplete service config";
267         finishConfiguration();
268 
269         if (mResourceResolver == null) {
270             // Abort the rendering if the resources are not found.
271             return null;
272         }
273 
274         int width = mWidth;
275         int height = mHeight;
276         if (mUseExplodeMode) {
277             // compute how many padding in x and y will bump the screen size
278             List<UiElementNode> children = mModel.getUiChildren();
279             if (children.size() == 1) {
280                 ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
281                         children.get(0).getXmlNode(), mProject);
282 
283                 // there are 2 paddings for each view
284                 // left and right, or top and bottom.
285                 int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
286 
287                 width += helper.getWidthPadding() * paddingValue;
288                 height += helper.getHeightPadding() * paddingValue;
289             }
290         }
291 
292         UiElementPullParser modelParser = new UiElementPullParser(mModel,
293                 mUseExplodeMode, mExpandNodes, mDensity, mXdpi, mProject);
294         ILayoutPullParser topParser = modelParser;
295 
296         // Code to support editing included layout
297         // first reset the layout parser just in case.
298         mProjectCallback.setLayoutParser(null, null);
299 
300         if (mIncludedWithin != null) {
301             // Outer layout name:
302             String contextLayoutName = mIncludedWithin.getName();
303 
304             // Find the layout file.
305             ResourceValue contextLayout = mResourceResolver.findResValue(
306                     LAYOUT_PREFIX + contextLayoutName, false  /* forceFrameworkOnly*/);
307             if (contextLayout != null) {
308                 File layoutFile = new File(contextLayout.getValue());
309                 if (layoutFile.isFile()) {
310                     try {
311                         // Get the name of the layout actually being edited, without the extension
312                         // as it's what IXmlPullParser.getParser(String) will receive.
313                         String queryLayoutName = mEditor.getLayoutResourceName();
314                         mProjectCallback.setLayoutParser(queryLayoutName, modelParser);
315                         topParser = new ContextPullParser(mProjectCallback, layoutFile);
316                         topParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
317                         topParser.setInput(new FileInputStream(layoutFile), "UTF-8"); //$NON-NLS-1$
318                     } catch (XmlPullParserException e) {
319                         AdtPlugin.log(e, ""); //$NON-NLS-1$
320                     } catch (FileNotFoundException e) {
321                         // this will not happen since we check above.
322                     }
323                 }
324             }
325         }
326 
327         SessionParams params = new SessionParams(
328                 topParser,
329                 mRenderingMode,
330                 mProject /* projectKey */,
331                 width, height,
332                 mDensity, mXdpi, mYdpi,
333                 mResourceResolver,
334                 mProjectCallback,
335                 mMinSdkVersion,
336                 mTargetSdkVersion,
337                 mLogger);
338 
339         // Request margin and baseline information.
340         // TODO: Be smarter about setting this; start without it, and on the first request
341         // for an extended view info, re-render in the same session, and then set a flag
342         // which will cause this to create extended view info each time from then on in the
343         // same session
344         params.setExtendedViewInfoMode(true);
345 
346         if (!mShowDecorations) {
347             params.setForceNoDecor();
348         } else {
349             ManifestInfo manifestInfo = ManifestInfo.get(mProject);
350             try {
351                 params.setAppLabel(manifestInfo.getApplicationLabel());
352                 params.setAppIcon(manifestInfo.getApplicationIcon());
353             } catch (Exception e) {
354                 // ignore.
355             }
356         }
357 
358         if (mScreenSize != null) {
359             params.setConfigScreenSize(mScreenSize.getValue());
360         }
361 
362         if (mOverrideBgColor != null) {
363             params.setOverrideBgColor(mOverrideBgColor.intValue());
364         }
365 
366         // set the Image Overlay as the image factory.
367         params.setImageFactory(mImageFactory);
368 
369         try {
370             mProjectCallback.setLogger(mLogger);
371             mProjectCallback.setResourceResolver(mResourceResolver);
372             return mLayoutLib.createSession(params);
373         } catch (RuntimeException t) {
374             // Exceptions from the bridge
375             mLogger.error(null, t.getLocalizedMessage(), t, null);
376             throw t;
377         } finally {
378             mProjectCallback.setLogger(null);
379             mProjectCallback.setResourceResolver(null);
380         }
381     }
382 
383     /**
384      * Renders the given resource value (which should refer to a drawable) and returns it
385      * as an image
386      *
387      * @param drawableResourceValue the drawable resource value to be rendered, or null
388      * @return the image, or null if something went wrong
389      */
renderDrawable(ResourceValue drawableResourceValue)390     public BufferedImage renderDrawable(ResourceValue drawableResourceValue) {
391         if (drawableResourceValue == null) {
392             return null;
393         }
394 
395         finishConfiguration();
396 
397         DrawableParams params = new DrawableParams(drawableResourceValue, mProject, mWidth, mHeight,
398                 mDensity, mXdpi, mYdpi, mResourceResolver, mProjectCallback, mMinSdkVersion,
399                 mTargetSdkVersion, mLogger);
400         params.setForceNoDecor();
401         Result result = mLayoutLib.renderDrawable(params);
402         if (result != null && result.isSuccess()) {
403             Object data = result.getData();
404             if (data instanceof BufferedImage) {
405                 return (BufferedImage) data;
406             }
407         }
408 
409         return null;
410     }
411 
412     /**
413      * Measure the children of the given parent node, applying the given filter to the
414      * pull parser's attribute values.
415      *
416      * @param parent the parent node to measure children for
417      * @param filter the filter to apply to the attribute values
418      * @return a map from node children of the parent to new bounds of the nodes
419      */
measureChildren(INode parent, final IClientRulesEngine.AttributeFilter filter)420     public Map<INode, Rect> measureChildren(INode parent,
421             final IClientRulesEngine.AttributeFilter filter) {
422         finishConfiguration();
423 
424         int width = parent.getBounds().w;
425         int height = parent.getBounds().h;
426 
427         final NodeFactory mNodeFactory = mEditor.getCanvasControl().getNodeFactory();
428         UiElementNode parentNode = ((NodeProxy) parent).getNode();
429         UiElementPullParser topParser = new UiElementPullParser(parentNode,
430                 false, Collections.<UiElementNode>emptySet(), mDensity, mXdpi, mProject) {
431             @Override
432             public String getAttributeValue(String namespace, String localName) {
433                 if (filter != null) {
434                     Object cookie = getViewCookie();
435                     if (cookie instanceof UiViewElementNode) {
436                         NodeProxy node = mNodeFactory.create((UiViewElementNode) cookie);
437                         if (node != null) {
438                             String value = filter.getAttribute(node, namespace, localName);
439                             if (value != null) {
440                                 return value;
441                             }
442                             // null means no preference, not "unset".
443                         }
444                     }
445                 }
446 
447                 return super.getAttributeValue(namespace, localName);
448             }
449 
450             /**
451              * The parser usually assumes that the top level node is a document node that
452              * should be skipped, and that's not the case when we render in the middle of
453              * the tree, so override {@link UiElementPullParser#onNextFromStartDocument}
454              * to change this behavior
455              */
456             @Override
457             public void onNextFromStartDocument() {
458                 mParsingState = START_TAG;
459             }
460         };
461 
462         SessionParams params = new SessionParams(
463                 topParser,
464                 RenderingMode.FULL_EXPAND,
465                 mProject /* projectKey */,
466                 width, height,
467                 mDensity, mXdpi, mYdpi,
468                 mResourceResolver,
469                 mProjectCallback,
470                 mMinSdkVersion,
471                 mTargetSdkVersion,
472                 mLogger);
473         params.setLayoutOnly();
474         params.setForceNoDecor();
475 
476         RenderSession session = null;
477         try {
478             mProjectCallback.setLogger(mLogger);
479             mProjectCallback.setResourceResolver(mResourceResolver);
480             session = mLayoutLib.createSession(params);
481             if (session.getResult().isSuccess()) {
482                 assert session.getRootViews().size() == 1;
483                 ViewInfo root = session.getRootViews().get(0);
484                 List<ViewInfo> children = root.getChildren();
485                 Map<INode, Rect> map = new HashMap<INode, Rect>(children.size());
486                 for (ViewInfo info : children) {
487                     if (info.getCookie() instanceof UiViewElementNode) {
488                         UiViewElementNode uiNode = (UiViewElementNode) info.getCookie();
489                         NodeProxy node = mNodeFactory.create(uiNode);
490                         map.put(node, new Rect(info.getLeft(), info.getTop(),
491                                 info.getRight() - info.getLeft(),
492                                 info.getBottom() - info.getTop()));
493                     }
494                 }
495 
496                 return map;
497             }
498         } catch (RuntimeException t) {
499             // Exceptions from the bridge
500             mLogger.error(null, t.getLocalizedMessage(), t, null);
501             throw t;
502         } finally {
503             mProjectCallback.setLogger(null);
504             mProjectCallback.setResourceResolver(null);
505             if (session != null) {
506                 session.dispose();
507             }
508         }
509 
510         return null;
511     }
512 }
513