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