• 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 
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.SessionParams.RenderingMode;
30 import com.android.ide.common.rendering.api.StyleResourceValue;
31 import com.android.ide.common.rendering.api.ViewInfo;
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.LayoutEditorDelegate;
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 
PreviewIconFactory(PaletteControl palette)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      */
reset()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      */
refresh()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      */
getImageDescriptor(ElementDescriptor desc)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      */
partitionRenderElements(Document document)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      */
render()205     private boolean render() {
206         File imageDir = getImageDir(true);
207 
208         GraphicalEditorPart editor = mPalette.getEditor();
209         LayoutEditorDelegate layoutEditorDelegate = editor.getEditorDelegate();
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 = layoutEditorDelegate.getEditor().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(layoutEditorDelegate.getEditor());
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      */
getColorsFromTheme()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      */
renderDrawableResource(String themeItemName)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 
resolveThemeColor(ResourceResolver resources, String resourceName)447     private static RGB resolveThemeColor(ResourceResolver resources, String resourceName) {
448         ResourceValue textColor = resources.findItemInTheme(resourceName);
449         return ResourceHelper.resolveColor(resources, textColor);
450     }
451 
getFileName(ElementDescriptor descriptor)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 
getFileName(UiElementNode node)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      */
cleanup(String name)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) */
getImageDir(boolean create)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-r16b-%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 
savePreview(File output, BufferedImage image, int left, int top, int right, int bottom)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 
storeBackground(File imageDir, RGB bg, RGB fg)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 
getBackgroundColor()552     public RGB getBackgroundColor() {
553         if (mBackground == null) {
554             initColors();
555         }
556 
557         return mBackground;
558     }
559 
getForegroundColor()560     public RGB getForegroundColor() {
561         if (mForeground == null) {
562             initColors();
563         }
564 
565         return mForeground;
566     }
567 
initColors()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