• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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.layoutlib.bridge.android;
18 
19 import com.android.ide.common.rendering.api.ILayoutLog;
20 import com.android.ide.common.rendering.api.RenderResources;
21 import com.android.ide.common.rendering.api.ResourceReference;
22 import com.android.ide.common.rendering.api.ResourceValue;
23 import com.android.ide.common.rendering.api.ResourceValueImpl;
24 import com.android.ide.common.rendering.api.StyleResourceValue;
25 import com.android.internal.graphics.ColorUtils;
26 import com.android.resources.ResourceType;
27 import com.android.systemui.monet.ColorScheme;
28 import com.android.systemui.monet.DynamicColors;
29 import com.android.systemui.monet.Style;
30 import com.android.systemui.monet.TonalPalette;
31 import com.android.tools.layoutlib.annotations.VisibleForTesting;
32 
33 import android.app.WallpaperColors;
34 import android.graphics.Bitmap;
35 import android.graphics.BitmapFactory;
36 import android.graphics.Color;
37 import android.util.Pair;
38 
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
46 
47 /**
48  * Wrapper for RenderResources that allows overriding default system colors
49  * when using dynamic theming.
50  */
51 public class DynamicRenderResources extends RenderResources {
52     private final RenderResources mBaseResources;
53     private Map<String, Integer> mDynamicColorMap;
54 
DynamicRenderResources(RenderResources baseResources)55     public DynamicRenderResources(RenderResources baseResources) {
56         mBaseResources = baseResources;
57     }
58 
59     @Override
setLogger(ILayoutLog logger)60     public void setLogger(ILayoutLog logger) {
61         mBaseResources.setLogger(logger);
62     }
63 
64     @Override
getDefaultTheme()65     public StyleResourceValue getDefaultTheme() {
66         return mBaseResources.getDefaultTheme();
67     }
68 
69     @Override
applyStyle(StyleResourceValue theme, boolean useAsPrimary)70     public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
71         mBaseResources.applyStyle(theme, useAsPrimary);
72     }
73 
74     @Override
clearStyles()75     public void clearStyles() {
76         mBaseResources.clearStyles();
77     }
78 
79     @Override
getAllThemes()80     public List<StyleResourceValue> getAllThemes() {
81         return mBaseResources.getAllThemes();
82     }
83 
84     @Override
findItemInTheme(ResourceReference attr)85     public ResourceValue findItemInTheme(ResourceReference attr) {
86         ResourceValue baseValue = mBaseResources.findItemInTheme(attr);
87         return resolveDynamicColors(baseValue);
88     }
89 
90     @Override
findItemInStyle(StyleResourceValue style, ResourceReference attr)91     public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
92         ResourceValue baseValue = mBaseResources.findItemInStyle(style, attr);
93         return resolveDynamicColors(baseValue);
94     }
95 
96     @Override
findResValue(String reference, boolean forceFrameworkOnly)97     public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
98         ResourceValue baseValue = mBaseResources.findResValue(reference, forceFrameworkOnly);
99         return resolveDynamicColors(baseValue);
100     }
101 
102     @Override
dereference(ResourceValue resourceValue)103     public ResourceValue dereference(ResourceValue resourceValue) {
104         ResourceValue baseValue = mBaseResources.dereference(resourceValue);
105         return resolveDynamicColors(baseValue);
106     }
107 
108     @Override
getUnresolvedResource(ResourceReference reference)109     public ResourceValue getUnresolvedResource(ResourceReference reference) {
110         ResourceValue baseValue = mBaseResources.getUnresolvedResource(reference);
111         return resolveDynamicColors(baseValue);
112     }
113 
114     @Override
getResolvedResource(ResourceReference reference)115     public ResourceValue getResolvedResource(ResourceReference reference) {
116         ResourceValue baseValue = mBaseResources.getResolvedResource(reference);
117         return resolveDynamicColors(baseValue);
118     }
119 
120     @Override
resolveResValue(ResourceValue value)121     public ResourceValue resolveResValue(ResourceValue value) {
122         ResourceValue baseValue = mBaseResources.resolveResValue(value);
123         return resolveDynamicColors(baseValue);
124     }
125 
126     @Override
getParent(StyleResourceValue style)127     public StyleResourceValue getParent(StyleResourceValue style) {
128         return mBaseResources.getParent(style);
129     }
130 
131     @Override
getStyle(ResourceReference reference)132     public StyleResourceValue getStyle(ResourceReference reference) {
133         return mBaseResources.getStyle(reference);
134     }
135 
resolveDynamicColors(ResourceValue baseValue)136     private ResourceValue resolveDynamicColors(ResourceValue baseValue) {
137         if (hasDynamicColors() && baseValue != null && isDynamicColor(baseValue)) {
138             int dynamicColor = mDynamicColorMap.get(baseValue.getName());
139             String colorHex = "#" + Integer.toHexString(dynamicColor).substring(2);
140             return new ResourceValueImpl(baseValue.getNamespace(), baseValue.getResourceType(),
141                     baseValue.getName(), colorHex);
142         }
143         return baseValue;
144     }
145 
setWallpaper(String wallpaperPath, boolean isNightMode)146     public void setWallpaper(String wallpaperPath, boolean isNightMode) {
147         if (wallpaperPath == null) {
148             mDynamicColorMap = null;
149             return;
150         }
151         mDynamicColorMap = createDynamicColorMap(wallpaperPath, isNightMode);
152     }
153 
154     /**
155      * Extracts colors from the wallpaper and creates the corresponding dynamic theme.
156      * It uses the main wallpaper color and the {@link Style#TONAL_SPOT} style.
157      *
158      * @param wallpaperPath path of the wallpaper resource to use
159      * @param isNightMode whether to use night mode or not
160      *
161      * @return map of system color names to their dynamic values
162      */
163     @VisibleForTesting
createDynamicColorMap(String wallpaperPath, boolean isNightMode)164     static Map<String, Integer> createDynamicColorMap(String wallpaperPath, boolean isNightMode) {
165         try (InputStream stream = DynamicRenderResources.class.getResourceAsStream(wallpaperPath)) {
166             Bitmap wallpaper = BitmapFactory.decodeStream(stream);
167             if (wallpaper == null) {
168                 return null;
169             }
170             WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(wallpaper);
171             int seed = ColorScheme.getSeedColor(wallpaperColors);
172             ColorScheme lightScheme = new ColorScheme(seed, false);
173             ColorScheme darkScheme = new ColorScheme(seed, true);
174             ColorScheme currentScheme = isNightMode ? darkScheme : lightScheme;
175             Map<String, Integer> dynamicColorMap = new HashMap<>();
176             extractPalette("accent1", dynamicColorMap, currentScheme.getAccent1());
177             extractPalette("accent2", dynamicColorMap, currentScheme.getAccent2());
178             extractPalette("accent3", dynamicColorMap, currentScheme.getAccent3());
179             extractPalette("neutral1", dynamicColorMap, currentScheme.getNeutral1());
180             extractPalette("neutral2", dynamicColorMap, currentScheme.getNeutral2());
181 
182             //Themed Colors
183             extractDynamicColors(dynamicColorMap, lightScheme, darkScheme,
184                     DynamicColors.getAllDynamicColorsMapped(false), false);
185             // Fixed Colors
186             extractDynamicColors(dynamicColorMap, lightScheme, darkScheme,
187                     DynamicColors.getFixedColorsMapped(false), true);
188             //Custom Colors
189             extractDynamicColors(dynamicColorMap, lightScheme, darkScheme,
190                     DynamicColors.getCustomColorsMapped(false), false);
191             return dynamicColorMap;
192         } catch (IllegalArgumentException | IOException ignore) {
193             return null;
194         }
195     }
196 
197     /**
198      * Builds the dynamic theme from the {@link ColorScheme} copying what is done
199      * in {@link ThemeOverlayController#getOverlay}
200      */
extractPalette(String name, Map<String, Integer> colorMap, TonalPalette tonalPalette)201     private static void extractPalette(String name,
202             Map<String, Integer> colorMap, TonalPalette tonalPalette) {
203         String resourcePrefix = "system_" + name;
204         tonalPalette.allShadesMapped.forEach((key, value) -> {
205             String resourceName = resourcePrefix + "_" + key;
206             int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
207             colorMap.put(resourceName, colorValue);
208         });
209         colorMap.put(resourcePrefix + "_0", Color.WHITE);
210     }
211 
extractDynamicColors(Map<String, Integer> colorMap, ColorScheme lightScheme, ColorScheme darkScheme, List<Pair<String, DynamicColor>> colors, Boolean isFixed)212     private static void extractDynamicColors(Map<String, Integer> colorMap, ColorScheme lightScheme,
213             ColorScheme darkScheme, List<Pair<String, DynamicColor>> colors, Boolean isFixed) {
214         colors.forEach(p -> {
215             String prefix = "system_" + p.first;
216 
217             if (isFixed) {
218                 colorMap.put(prefix, p.second.getArgb(lightScheme.getMaterialScheme()));
219                 return;
220             }
221 
222             colorMap.put(prefix + "_light", p.second.getArgb(lightScheme.getMaterialScheme()));
223             colorMap.put(prefix + "_dark", p.second.getArgb(darkScheme.getMaterialScheme()));
224         });
225     }
226 
isDynamicColor(ResourceValue resourceValue)227     private boolean isDynamicColor(ResourceValue resourceValue) {
228         if (!resourceValue.isFramework() || resourceValue.getResourceType() != ResourceType.COLOR) {
229             return false;
230         }
231         return mDynamicColorMap.containsKey(resourceValue.getName());
232     }
233 
hasDynamicColors()234     public boolean hasDynamicColors() {
235         return mDynamicColorMap != null;
236     }
237 }
238