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 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.FQCN_DATE_PICKER;
     20 import static com.android.ide.common.layout.LayoutConstants.FQCN_EXPANDABLE_LIST_VIEW;
     21 import static com.android.ide.common.layout.LayoutConstants.FQCN_LIST_VIEW;
     22 import static com.android.ide.common.layout.LayoutConstants.FQCN_TIME_PICKER;
     23 import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG;
     24 
     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.RenderSession;
     28 import com.android.ide.common.rendering.api.ResourceValue;
     29 import com.android.ide.common.rendering.api.StyleResourceValue;
     30 import com.android.ide.common.rendering.api.ViewInfo;
     31 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
     32 import com.android.ide.common.resources.ResourceResolver;
     33 import com.android.ide.eclipse.adt.AdtPlugin;
     34 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
     35 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     36 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
     37 import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor;
     38 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
     39 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode;
     40 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
     41 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     42 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     43 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     44 import com.android.sdklib.IAndroidTarget;
     45 import com.android.util.Pair;
     46 
     47 import org.eclipse.core.runtime.IPath;
     48 import org.eclipse.core.runtime.IStatus;
     49 import org.eclipse.jface.resource.ImageDescriptor;
     50 import org.eclipse.swt.graphics.RGB;
     51 import org.w3c.dom.Document;
     52 import org.w3c.dom.Element;
     53 import org.w3c.dom.Node;
     54 import org.w3c.dom.NodeList;
     55 
     56 import java.awt.image.BufferedImage;
     57 import java.io.BufferedInputStream;
     58 import java.io.File;
     59 import java.io.FileInputStream;
     60 import java.io.IOException;
     61 import java.io.InputStream;
     62 import java.net.MalformedURLException;
     63 import java.util.ArrayList;
     64 import java.util.Collections;
     65 import java.util.List;
     66 import java.util.Properties;
     67 
     68 import javax.imageio.ImageIO;
     69 
     70 /**
     71  * Factory which can provide preview icons for android views of a particular SDK and
     72  * editor's configuration chooser
     73  */
     74 public class PreviewIconFactory {
     75     private PaletteControl mPalette;
     76     private RGB mBackground;
     77     private RGB mForeground;
     78     private File mImageDir;
     79 
     80     private static final String PREVIEW_INFO_FILE = "preview.properties"; //$NON-NLS-1$
     81 
     82     public PreviewIconFactory(PaletteControl palette) {
     83         mPalette = palette;
     84     }
     85 
     86     /**
     87      * Resets the state in the preview icon factory such that it will re-fetch information
     88      * like the theme and SDK (the icons themselves are cached in a directory across IDE
     89      * session though)
     90      */
     91     public void reset() {
     92         mImageDir = null;
     93         mBackground = null;
     94         mForeground = null;
     95     }
     96 
     97     /**
     98      * Deletes all the persistent state for the current settings such that it will be regenerated
     99      */
    100     public void refresh() {
    101         File imageDir = getImageDir(false);
    102         if (imageDir != null && imageDir.exists()) {
    103             File[] files = imageDir.listFiles();
    104             for (File file : files) {
    105                 file.delete();
    106             }
    107             imageDir.delete();
    108             reset();
    109         }
    110     }
    111 
    112     /**
    113      * Returns an image descriptor for the given element descriptor, or null if no image
    114      * could be computed. The rendering parameters (SDK, theme etc) correspond to those
    115      * stored in the associated palette.
    116      *
    117      * @param desc the element descriptor to get an image for
    118      * @return an image descriptor, or null if no image could be rendered
    119      */
    120     public ImageDescriptor getImageDescriptor(ElementDescriptor desc) {
    121         File imageDir = getImageDir(false);
    122         if (!imageDir.exists()) {
    123             render();
    124         }
    125         File file = new File(imageDir, getFileName(desc));
    126         if (file.exists()) {
    127             try {
    128                 return ImageDescriptor.createFromURL(file.toURI().toURL());
    129             } catch (MalformedURLException e) {
    130                 AdtPlugin.log(e, "Could not create image descriptor for %s", file);
    131             }
    132         }
    133 
    134         return null;
    135     }
    136 
    137     /**
    138      * Partition the elements in the document according to their rendering preferences;
    139      * elements that should be skipped are removed, elements that should be rendered alone
    140      * are placed in their own list, etc
    141      *
    142      * @param document the document containing render fragments for the various elements
    143      * @return
    144      */
    145     private List<List<Element>> partitionRenderElements(Document document) {
    146         List<List<Element>> elements = new ArrayList<List<Element>>();
    147 
    148         List<Element> shared = new ArrayList<Element>();
    149         Element root = document.getDocumentElement();
    150         elements.add(shared);
    151 
    152         ViewMetadataRepository repository = ViewMetadataRepository.get();
    153 
    154         NodeList children = root.getChildNodes();
    155         for (int i = 0, n = children.getLength(); i < n; i++) {
    156             Node node = children.item(i);
    157             if (node.getNodeType() == Node.ELEMENT_NODE) {
    158                 Element element = (Element) node;
    159                 String fqn = repository.getFullClassName(element);
    160                 assert fqn.length() > 0 : element.getNodeName();
    161                 RenderMode renderMode = repository.getRenderMode(fqn);
    162 
    163                 // Temporary special cases
    164                 if (fqn.equals(FQCN_LIST_VIEW) || fqn.equals(FQCN_EXPANDABLE_LIST_VIEW)) {
    165                     if (!mPalette.getEditor().renderingSupports(Capability.ADAPTER_BINDING)) {
    166                         renderMode = RenderMode.SKIP;
    167                     }
    168                 } else if (fqn.equals(FQCN_DATE_PICKER) || fqn.equals(FQCN_TIME_PICKER)) {
    169                     IAndroidTarget renderingTarget = mPalette.getEditor().getRenderingTarget();
    170                     // In Honeycomb, these widgets only render properly in the Holo themes.
    171                     int apiLevel = renderingTarget.getVersion().getApiLevel();
    172                     if (apiLevel == 11) {
    173                         String themeName = mPalette.getCurrentTheme();
    174                         if (themeName == null || !themeName.startsWith("Theme.Holo")) { //$NON-NLS-1$
    175                             // Note - it's possible that the the theme is some other theme
    176                             // such as a user theme which inherits from Theme.Holo and that
    177                             // the render -would- have worked, but it's harder to detect that
    178                             // scenario, so we err on the side of caution and just show an
    179                             // icon + name for the time widgets.
    180                             renderMode = RenderMode.SKIP;
    181                         }
    182                     } else if (apiLevel >= 12) {
    183                         // Currently broken, even for Holo.
    184                         renderMode = RenderMode.SKIP;
    185                     } // apiLevel <= 10 is fine
    186                 }
    187 
    188                 if (renderMode == RenderMode.ALONE) {
    189                     elements.add(Collections.singletonList(element));
    190                 } else if (renderMode == RenderMode.NORMAL) {
    191                     shared.add(element);
    192                 } else {
    193                     assert renderMode == RenderMode.SKIP;
    194                 }
    195             }
    196         }
    197 
    198         return elements;
    199     }
    200 
    201     /**
    202      * Renders ALL the widgets and then extracts image data for each view and saves it on
    203      * disk
    204      */
    205     private boolean render() {
    206         File imageDir = getImageDir(true);
    207 
    208         GraphicalEditorPart editor = mPalette.getEditor();
    209         LayoutEditor layoutEditor = editor.getLayoutEditor();
    210         LayoutLibrary layoutLibrary = editor.getLayoutLibrary();
    211         Integer overrideBgColor = null;
    212         if (layoutLibrary != null) {
    213             if (layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR)) {
    214                 Pair<RGB, RGB> themeColors = getColorsFromTheme();
    215                 RGB bg = themeColors.getFirst();
    216                 RGB fg = themeColors.getSecond();
    217                 if (bg != null) {
    218                     storeBackground(imageDir, bg, fg);
    219                     overrideBgColor = Integer.valueOf(ImageUtils.rgbToInt(bg, 0xFF));
    220                 }
    221             }
    222         }
    223 
    224         ViewMetadataRepository repository = ViewMetadataRepository.get();
    225         Document document = repository.getRenderingConfigDoc();
    226 
    227         if (document == null) {
    228             return false;
    229         }
    230 
    231         // Construct UI model from XML
    232         AndroidTargetData data = layoutEditor.getTargetData();
    233         DocumentDescriptor documentDescriptor;
    234         if (data == null) {
    235             documentDescriptor = new DocumentDescriptor("temp", null/*children*/);//$NON-NLS-1$
    236         } else {
    237             documentDescriptor = data.getLayoutDescriptors().getDescriptor();
    238         }
    239         UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode();
    240         model.setEditor(layoutEditor);
    241         model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider());
    242 
    243         Element documentElement = document.getDocumentElement();
    244         List<List<Element>> elements = partitionRenderElements(document);
    245         for (List<Element> elementGroup : elements) {
    246             // Replace the document elements with the current element group
    247             while (documentElement.getFirstChild() != null) {
    248                 documentElement.removeChild(documentElement.getFirstChild());
    249             }
    250             for (Element element : elementGroup) {
    251                 documentElement.appendChild(element);
    252             }
    253 
    254             model.loadFromXmlNode(document);
    255 
    256             RenderSession session = null;
    257             NodeList childNodes = documentElement.getChildNodes();
    258             try {
    259                 // Important to get these sizes large enough for clients that don't support
    260                 // RenderMode.FULL_EXPAND such as 1.6
    261                 int width = 200;
    262                 int height = childNodes.getLength() == 1 ? 400 : 1600;
    263 
    264                 session = RenderService.create(editor)
    265                     .setModel(model)
    266                     .setSize(width, height)
    267                     .setRenderingMode(RenderingMode.FULL_EXPAND)
    268                     .setLog(new RenderLogger("palette"))
    269                     .setOverrideBgColor(overrideBgColor)
    270                     .setDecorations(false)
    271                     .createRenderSession();
    272             } catch (Throwable t) {
    273                 // If there are internal errors previewing the components just revert to plain
    274                 // icons and labels
    275                 continue;
    276             }
    277 
    278             if (session != null) {
    279                 if (session.getResult().isSuccess()) {
    280                     BufferedImage image = session.getImage();
    281                     if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
    282 
    283                         // Fallback for older platforms where we couldn't do background rendering
    284                         // at the beginning of this method
    285                         if (mBackground == null) {
    286                             Pair<RGB, RGB> themeColors = getColorsFromTheme();
    287                             RGB bg = themeColors.getFirst();
    288                             RGB fg = themeColors.getSecond();
    289 
    290                             if (bg == null) {
    291                                 // Just use a pixel from the rendering instead.
    292                                 int p = image.getRGB(image.getWidth() - 1, image.getHeight() - 1);
    293                                 // However, in this case we don't trust the foreground color
    294                                 // even if one was found in the themes; pick one that is guaranteed
    295                                 // to contrast with the background
    296                                 bg = ImageUtils.intToRgb(p);
    297                                 if (ImageUtils.getBrightness(ImageUtils.rgbToInt(bg, 255)) < 128) {
    298                                     fg = new RGB(255, 255, 255);
    299                                 } else {
    300                                     fg = new RGB(0, 0, 0);
    301                                 }
    302                             }
    303                             storeBackground(imageDir, bg, fg);
    304                             assert mBackground != null;
    305                         }
    306 
    307                         List<ViewInfo> viewInfoList = session.getRootViews();
    308                         if (viewInfoList != null && viewInfoList.size() > 0) {
    309                             // We don't render previews under a <merge> so there should
    310                             // only be one root.
    311                             ViewInfo firstRoot = viewInfoList.get(0);
    312                             int parentX = firstRoot.getLeft();
    313                             int parentY = firstRoot.getTop();
    314                             List<ViewInfo> infos = firstRoot.getChildren();
    315                             for (ViewInfo info : infos) {
    316                                 Object cookie = info.getCookie();
    317                                 if (!(cookie instanceof UiElementNode)) {
    318                                     continue;
    319                                 }
    320                                 UiElementNode node = (UiElementNode) cookie;
    321                                 String fileName = getFileName(node);
    322                                 File file = new File(imageDir, fileName);
    323                                 if (file.exists()) {
    324                                     // On Windows, perhaps we need to rename instead?
    325                                     file.delete();
    326                                 }
    327                                 int x1 = parentX + info.getLeft();
    328                                 int y1 = parentY + info.getTop();
    329                                 int x2 = parentX + info.getRight();
    330                                 int y2 = parentY + info.getBottom();
    331                                 if (x1 != x2 && y1 != y2) {
    332                                     savePreview(file, image, x1, y1, x2, y2);
    333                                 }
    334                             }
    335                         }
    336                     }
    337                 } else {
    338                     StringBuilder sb = new StringBuilder();
    339                     for (int i = 0, n = childNodes.getLength(); i < n; i++) {
    340                         Node node = childNodes.item(i);
    341                         if (node instanceof Element) {
    342                             Element e = (Element) node;
    343                             String fqn = repository.getFullClassName(e);
    344                             fqn = fqn.substring(fqn.lastIndexOf('.') + 1);
    345                             if (sb.length() > 0) {
    346                                 sb.append(", "); //$NON-NLS-1$
    347                             }
    348                             sb.append(fqn);
    349                         }
    350                     }
    351                     AdtPlugin.log(IStatus.WARNING, "Failed to render set of icons for %1$s",
    352                             sb.toString());
    353 
    354                     if (session.getResult().getException() != null) {
    355                         AdtPlugin.log(session.getResult().getException(),
    356                                 session.getResult().getErrorMessage());
    357                     } else if (session.getResult().getErrorMessage() != null) {
    358                         AdtPlugin.log(IStatus.WARNING, session.getResult().getErrorMessage());
    359                     }
    360                 }
    361 
    362                 session.dispose();
    363             }
    364         }
    365 
    366         return true;
    367     }
    368 
    369     /**
    370      * Look up the background and foreground colors from the theme. May not find either
    371      * the background or foreground or both, but will always return a pair of possibly
    372      * null colors.
    373      *
    374      * @return a pair of possibly null color descriptions
    375      */
    376     private Pair<RGB, RGB> getColorsFromTheme() {
    377         RGB background = null;
    378         RGB foreground = null;
    379 
    380         ResourceResolver resources = mPalette.getEditor().getResourceResolver();
    381         StyleResourceValue theme = resources.getCurrentTheme();
    382         if (theme != null) {
    383             background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$
    384             if (background == null) {
    385                 background = renderDrawableResource("windowBackground"); //$NON-NLS-1$
    386                 // This causes some harm with some themes: We'll find a color, say black,
    387                 // that isn't actually rendered in the theme. Better to use null here,
    388                 // which will cause the caller to pick a pixel from the observed background
    389                 // instead.
    390                 //if (background == null) {
    391                 //    background = resolveThemeColor(resources, "colorBackground"); //$NON-NLS-1$
    392                 //}
    393             }
    394             foreground = resolveThemeColor(resources, "textColorPrimary"); //$NON-NLS-1$
    395         }
    396 
    397         // Ensure that the foreground color is suitably distinct from the background color
    398         if (background != null) {
    399             int bgRgb = ImageUtils.rgbToInt(background, 0xFF);
    400             int backgroundBrightness = ImageUtils.getBrightness(bgRgb);
    401             if (foreground == null) {
    402                 if (backgroundBrightness < 128) {
    403                     foreground = new RGB(255, 255, 255);
    404                 } else {
    405                     foreground = new RGB(0, 0, 0);
    406                 }
    407             } else {
    408                 int fgRgb = ImageUtils.rgbToInt(foreground, 0xFF);
    409                 int foregroundBrightness = ImageUtils.getBrightness(fgRgb);
    410                 if (Math.abs(backgroundBrightness - foregroundBrightness) < 64) {
    411                     if (backgroundBrightness < 128) {
    412                         foreground = new RGB(255, 255, 255);
    413                     } else {
    414                         foreground = new RGB(0, 0, 0);
    415                     }
    416                 }
    417             }
    418         }
    419 
    420         return Pair.of(background, foreground);
    421     }
    422 
    423     /**
    424      * Renders the given resource which should refer to a drawable and returns a
    425      * representative color value for the drawable (such as the color in the center)
    426      *
    427      * @param themeItemName the item in the theme to be looked up and rendered
    428      * @return a color representing a typical color in the drawable
    429      */
    430     private RGB renderDrawableResource(String themeItemName) {
    431         GraphicalEditorPart editor = mPalette.getEditor();
    432         ResourceResolver resources = editor.getResourceResolver();
    433         ResourceValue resourceValue = resources.findItemInTheme(themeItemName);
    434         BufferedImage image = RenderService.create(editor)
    435             .setSize(100, 100)
    436             .renderDrawable(resourceValue);
    437         if (image != null) {
    438             // Use the middle pixel as the color since that works better for gradients;
    439             // solid colors work too.
    440             int rgb = image.getRGB(image.getWidth() / 2, image.getHeight() / 2);
    441             return ImageUtils.intToRgb(rgb);
    442         }
    443 
    444         return null;
    445     }
    446 
    447     private static RGB resolveThemeColor(ResourceResolver resources, String resourceName) {
    448         ResourceValue textColor = resources.findItemInTheme(resourceName);
    449         return ResourceHelper.resolveColor(resources, textColor);
    450     }
    451 
    452     private String getFileName(ElementDescriptor descriptor) {
    453         if (descriptor instanceof PaletteMetadataDescriptor) {
    454             PaletteMetadataDescriptor pmd = (PaletteMetadataDescriptor) descriptor;
    455             StringBuilder sb = new StringBuilder();
    456             String name = pmd.getUiName();
    457             // Strip out whitespace, parentheses, etc.
    458             for (int i = 0, n = name.length(); i < n; i++) {
    459                 char c = name.charAt(i);
    460                 if (Character.isLetter(c)) {
    461                     sb.append(c);
    462                 }
    463             }
    464             return sb.toString() + DOT_PNG;
    465         }
    466         return descriptor.getUiName() + DOT_PNG;
    467     }
    468 
    469     private String getFileName(UiElementNode node) {
    470         ViewMetadataRepository repository = ViewMetadataRepository.get();
    471         String fqn = repository.getFullClassName((Element) node.getXmlNode());
    472         return fqn.substring(fqn.lastIndexOf('.') + 1) + DOT_PNG;
    473     }
    474 
    475     /**
    476      * Cleans up a name by removing punctuation and whitespace etc to make
    477      * it a better filename
    478      * @param name
    479      * @return
    480      */
    481     private static String cleanup(String name) {
    482         // Extract just the characters (no whitespace, parentheses, punctuation etc)
    483         // to ensure that the filename is pretty portable
    484         StringBuilder sb = new StringBuilder(name.length());
    485         for (int i = 0; i < name.length(); i++) {
    486             char c = name.charAt(i);
    487             if (Character.isJavaIdentifierPart(c)) {
    488                 sb.append(Character.toLowerCase(c));
    489             }
    490         }
    491 
    492         return sb.toString();
    493     }
    494 
    495     /** Returns the location of a directory containing image previews (which may not exist) */
    496     private File getImageDir(boolean create) {
    497         if (mImageDir == null) {
    498             // Location for plugin-related state data
    499             IPath pluginState = AdtPlugin.getDefault().getStateLocation();
    500 
    501             // We have multiple directories - one for each combination of SDK, theme and device
    502             // (and later, possibly other qualifiers).
    503             // These are created -lazily-.
    504             String targetName = mPalette.getCurrentTarget().hashString();
    505             String androidTargetNamePrefix = "android-";
    506             String themeNamePrefix = "Theme.";
    507             if (targetName.startsWith(androidTargetNamePrefix)) {
    508                 targetName = targetName.substring(androidTargetNamePrefix.length());
    509             }
    510             String themeName = mPalette.getCurrentTheme();
    511             if (themeName == null) {
    512                 themeName = "Theme"; //$NON-NLS-1$
    513             }
    514             if (themeName.startsWith(themeNamePrefix)) {
    515                 themeName = themeName.substring(themeNamePrefix.length());
    516             }
    517             String dirName = String.format("palette-preview-r11d-%s-%s-%s", cleanup(targetName),
    518                     cleanup(themeName), cleanup(mPalette.getCurrentDevice()));
    519             IPath dirPath = pluginState.append(dirName);
    520 
    521             mImageDir = new File(dirPath.toOSString());
    522         }
    523 
    524         if (create && !mImageDir.exists()) {
    525             mImageDir.mkdirs();
    526         }
    527 
    528         return mImageDir;
    529     }
    530 
    531     private void savePreview(File output, BufferedImage image,
    532             int left, int top, int right, int bottom) {
    533         try {
    534             BufferedImage im = ImageUtils.subImage(image, left, top, right, bottom);
    535             ImageIO.write(im, "PNG", output); //$NON-NLS-1$
    536         } catch (IOException e) {
    537             AdtPlugin.log(e, "Failed writing palette file");
    538         }
    539     }
    540 
    541     private void storeBackground(File imageDir, RGB bg, RGB fg) {
    542         mBackground = bg;
    543         mForeground = fg;
    544         File file = new File(imageDir, PREVIEW_INFO_FILE);
    545         String colors = String.format(
    546                 "background=#%02x%02x%02x\nforeground=#%02x%02x%02x\n", //$NON-NLS-1$
    547                 bg.red, bg.green, bg.blue,
    548                 fg.red, fg.green, fg.blue);
    549         AdtPlugin.writeFile(file, colors);
    550     }
    551 
    552     public RGB getBackgroundColor() {
    553         if (mBackground == null) {
    554             initColors();
    555         }
    556 
    557         return mBackground;
    558     }
    559 
    560     public RGB getForegroundColor() {
    561         if (mForeground == null) {
    562             initColors();
    563         }
    564 
    565         return mForeground;
    566     }
    567 
    568     public void initColors() {
    569         try {
    570             // Already initialized? Foreground can be null which would call
    571             // initColors again and again, but background is never null after
    572             // initialization so we use it as the have-initialized flag.
    573             if (mBackground != null) {
    574                 return;
    575             }
    576 
    577             File imageDir = getImageDir(false);
    578             if (!imageDir.exists()) {
    579                 render();
    580 
    581                 // Initialized as part of the render
    582                 if (mBackground != null) {
    583                     return;
    584                 }
    585             }
    586 
    587             File file = new File(imageDir, PREVIEW_INFO_FILE);
    588             if (file.exists()) {
    589                 Properties properties = new Properties();
    590                 InputStream is = null;
    591                 try {
    592                     is = new BufferedInputStream(new FileInputStream(file));
    593                     properties.load(is);
    594                 } catch (IOException e) {
    595                     AdtPlugin.log(e, "Can't read preview properties");
    596                 } finally {
    597                     if (is != null) {
    598                         try {
    599                             is.close();
    600                         } catch (IOException e) {
    601                             // Nothing useful can be done.
    602                         }
    603                     }
    604                 }
    605 
    606                 String colorString = (String) properties.get("background"); //$NON-NLS-1$
    607                 if (colorString != null) {
    608                     int rgb = ImageUtils.getColor(colorString.trim());
    609                     mBackground = ImageUtils.intToRgb(rgb);
    610                 }
    611                 colorString = (String) properties.get("foreground"); //$NON-NLS-1$
    612                 if (colorString != null) {
    613                     int rgb = ImageUtils.getColor(colorString.trim());
    614                     mForeground = ImageUtils.intToRgb(rgb);
    615                 }
    616             }
    617 
    618             if (mBackground == null) {
    619                 mBackground = new RGB(0, 0, 0);
    620             }
    621             // mForeground is allowed to be null.
    622         } catch (Throwable t) {
    623             AdtPlugin.log(t, "Cannot initialize preview color settings");
    624         }
    625     }
    626 }
    627