aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJerome Gaillard <jgaillard@google.com>2022-06-06 18:02:13 +0100
committerJerome Gaillard <jgaillard@google.com>2023-02-07 18:44:53 +0000
commit6dc349b485562a32231ad559519d0efc619db298 (patch)
tree448dbf0354d8eb19d90ad4228612c0e5ee6d716a
parent4750e1cdf2158de3d76cbbbe9b8d159683caa3a0 (diff)
downloadlayoutlib-6dc349b485562a32231ad559519d0efc619db298.tar.gz
Add dynamic theming to layoutlib
Layoutlib receives the path to an image file to use as a wallpaper. It creates a dynamic theme reusing/copying what the framework is doing. It sets the dynamic theme by overriding access to system colors through the DynamicRenderResources wrapper. Bug: 227467236 Test: tests added Change-Id: Id37b531c5f29e57c37f5390804ddc58d27a9eeb0 (cherry picked from commit c8a05ec2abe059f44ea4371d71dbdcb2630ce4ec) Merged-In: Id37b531c5f29e57c37f5390804ddc58d27a9eeb0
-rw-r--r--.idea/libraries/kotlin_stdlib.xml9
-rw-r--r--Android.bp2
-rw-r--r--bridge/bridge.iml1
-rw-r--r--bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java42
-rw-r--r--bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java13
-rw-r--r--bridge/src/com/android/layoutlib/bridge/android/DynamicRenderResources.java219
-rw-r--r--bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java7
-rw-r--r--bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java1
-rw-r--r--bridge/tests/res/com/android/layoutlib/testdata/wallpaper1.webpbin0 -> 175870 bytes
-rw-r--r--bridge/tests/res/com/android/layoutlib/testdata/wallpaper2.webpbin0 -> 486114 bytes
-rw-r--r--bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.pngbin0 -> 25586 bytes
-rw-r--r--bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.pngbin0 -> 25703 bytes
-rw-r--r--bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml1
-rwxr-xr-xbridge/tests/run_tests.sh2
-rw-r--r--bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java49
-rw-r--r--bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java69
-rw-r--r--bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java73
-rw-r--r--common/src/com/android/tools/layoutlib/create/NativeConfig.java1
-rw-r--r--create/src/com/android/tools/layoutlib/create/CreateInfo.java1
-rw-r--r--create/src/com/android/tools/layoutlib/create/Main.java1
20 files changed, 488 insertions, 3 deletions
diff --git a/.idea/libraries/kotlin_stdlib.xml b/.idea/libraries/kotlin_stdlib.xml
new file mode 100644
index 0000000000..9673da8948
--- /dev/null
+++ b/.idea/libraries/kotlin_stdlib.xml
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+ <library name="kotlin-stdlib">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../external/kotlinc/lib/kotlin-stdlib.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+</component> \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 4461b77d2b..e81683853c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -37,6 +37,7 @@ java_genrule_host {
":ext{.jar}",
":icu4j-icudata-jarjar{.jar}", // HOST
":icu4j-icutzdata-jarjar{.jar}", // HOST
+ ":monet{.jar}",
],
cmd: "rm -f $(out) && $(location layoutlib_create) --create-stub $(out) $(in)",
}
@@ -51,5 +52,6 @@ java_device_for_host {
"framework-all",
"icu4j-icudata-jarjar",
"icu4j-icutzdata-jarjar",
+ "monet",
],
}
diff --git a/bridge/bridge.iml b/bridge/bridge.iml
index 62d9cf8318..bc0f30d074 100644
--- a/bridge/bridge.iml
+++ b/bridge/bridge.iml
@@ -93,5 +93,6 @@
</library>
</orderEntry>
<orderEntry type="module" module-name="validator" />
+ <orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
</component>
</module> \ No newline at end of file
diff --git a/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java b/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
index 3978af0701..fe307cab68 100644
--- a/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
+++ b/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
@@ -17,8 +17,12 @@
package android.graphics.drawable;
import com.android.internal.R;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.content.res.Resources;
+import android.content.res.Resources_Delegate;
+import android.graphics.Canvas;
public class AdaptiveIconDrawable_Delegate {
public static String sPath;
@@ -35,4 +39,42 @@ public class AdaptiveIconDrawable_Delegate {
}
return res.getString(resId);
}
+
+ @LayoutlibDelegate
+ public static void draw(AdaptiveIconDrawable thisDrawable, Canvas canvas) {
+ Resources res = Resources.getSystem();
+ BridgeContext context = Resources_Delegate.getContext(res);
+ if (context.hasDynamicColors()) {
+ AdaptiveIconDrawable themedIcon = createThemedVersion(thisDrawable, res);
+ themedIcon.onBoundsChange(thisDrawable.getBounds());
+ themedIcon.draw_Original(canvas);
+ } else {
+ thisDrawable.draw_Original(canvas);
+ }
+ }
+
+ /**
+ * This builds the themed version of {@link AdaptiveIconDrawable}, copying what the
+ * framework does in {@link com.android.launcher3.Utilities#getFullDrawable}
+ */
+ private static AdaptiveIconDrawable createThemedVersion(AdaptiveIconDrawable base,
+ Resources resources) {
+ Drawable mono = base.getMonochrome();
+ mono = mono.mutate();
+ int[] colors = getColors(resources);
+ mono.setTint(colors[1]);
+ return new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
+ }
+
+ private static int[] getColors(Resources resources) {
+ int[] colors = new int[2];
+ if (resources.getConfiguration().isNightModeActive()) {
+ colors[0] = resources.getColor(android.R.color.system_neutral1_800, null);
+ colors[1] = resources.getColor(android.R.color.system_accent1_100, null);
+ } else {
+ colors[0] = resources.getColor(android.R.color.system_accent1_100, null);
+ colors[1] = resources.getColor(android.R.color.system_neutral2_700, null);
+ }
+ return colors;
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index c3b5ede7c5..b3f985afd6 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -70,6 +70,7 @@ import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
@@ -164,7 +165,7 @@ public class BridgeContext extends Context {
private Resources mSystemResources;
private final Object mProjectKey;
private final DisplayMetrics mMetrics;
- private final RenderResources mRenderResources;
+ private final DynamicRenderResources mRenderResources;
private final Configuration mConfig;
private final ApplicationInfo mApplicationInfo;
private final LayoutlibCallback mLayoutlibCallback;
@@ -242,7 +243,7 @@ public class BridgeContext extends Context {
mMetrics = metrics;
mLayoutlibCallback = layoutlibCallback;
- mRenderResources = renderResources;
+ mRenderResources = new DynamicRenderResources(renderResources);
mConfig = config;
AssetManager systemAssetManager = AssetManager.getSystem();
if (systemAssetManager instanceof BridgeAssetManager) {
@@ -2271,4 +2272,12 @@ public class BridgeContext extends Context {
public SessionInteractiveData getSessionInteractiveData() {
return mSessionInteractiveData;
}
+
+ public boolean hasDynamicColors() {
+ return mRenderResources.hasDynamicColors();
+ }
+
+ public void applyWallpaper(String wallpaperPath) {
+ mRenderResources.setWallpaper(wallpaperPath, mConfig.isNightModeActive());
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/DynamicRenderResources.java b/bridge/src/com/android/layoutlib/bridge/android/DynamicRenderResources.java
new file mode 100644
index 0000000000..2f01a85f9d
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/android/DynamicRenderResources.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.ILayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.internal.graphics.ColorUtils;
+import com.android.resources.ResourceType;
+import com.android.systemui.monet.ColorScheme;
+import com.android.systemui.monet.Style;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Wrapper for RenderResources that allows overriding default system colors
+ * when using dynamic theming.
+ */
+public class DynamicRenderResources extends RenderResources {
+ private final RenderResources mBaseResources;
+ private Map<String, Integer> mDynamicColorMap;
+
+ public DynamicRenderResources(RenderResources baseResources) {
+ mBaseResources = baseResources;
+ }
+
+ @Override
+ public void setLogger(ILayoutLog logger) {
+ mBaseResources.setLogger(logger);
+ }
+
+ @Override
+ public StyleResourceValue getDefaultTheme() {
+ return mBaseResources.getDefaultTheme();
+ }
+
+ @Override
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ mBaseResources.applyStyle(theme, useAsPrimary);
+ }
+
+ @Override
+ public void clearStyles() {
+ mBaseResources.clearStyles();
+ }
+
+ @Override
+ public List<StyleResourceValue> getAllThemes() {
+ return mBaseResources.getAllThemes();
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(ResourceReference attr) {
+ ResourceValue baseValue = mBaseResources.findItemInTheme(attr);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
+ ResourceValue baseValue = mBaseResources.findItemInStyle(style, attr);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+ ResourceValue baseValue = mBaseResources.findResValue(reference, forceFrameworkOnly);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue dereference(ResourceValue resourceValue) {
+ ResourceValue baseValue = mBaseResources.dereference(resourceValue);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue getUnresolvedResource(ResourceReference reference) {
+ ResourceValue baseValue = mBaseResources.getUnresolvedResource(reference);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue getResolvedResource(ResourceReference reference) {
+ ResourceValue baseValue = mBaseResources.getResolvedResource(reference);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue value) {
+ ResourceValue baseValue = mBaseResources.resolveResValue(value);
+ return resolveDynamicColors(baseValue);
+ }
+
+ @Override
+ public StyleResourceValue getParent(StyleResourceValue style) {
+ return mBaseResources.getParent(style);
+ }
+
+ @Override
+ public StyleResourceValue getStyle(ResourceReference reference) {
+ return mBaseResources.getStyle(reference);
+ }
+
+ private ResourceValue resolveDynamicColors(ResourceValue baseValue) {
+ if (hasDynamicColors() && baseValue != null && isDynamicColor(baseValue)) {
+ int dynamicColor = mDynamicColorMap.get(baseValue.getName());
+ String colorHex = "#" + Integer.toHexString(dynamicColor).substring(2);
+ return new ResourceValueImpl(baseValue.getNamespace(), baseValue.getResourceType(),
+ baseValue.getName(), colorHex);
+ }
+ return baseValue;
+ }
+
+ public void setWallpaper(String wallpaperPath, boolean isNightMode) {
+ if (wallpaperPath == null) {
+ mDynamicColorMap = null;
+ return;
+ }
+ try {
+ Bitmap wallpaper = BitmapFactory.decodeFile(wallpaperPath);
+ mDynamicColorMap = createDynamicColorMap(wallpaper, isNightMode);
+ } catch (IllegalArgumentException ignore) {
+ mDynamicColorMap = null;
+ }
+ }
+
+ /**
+ * Extracts colors from the wallpaper and creates the corresponding dynamic theme.
+ * It uses the main wallpaper color and the {@link Style#TONAL_SPOT} style.
+ *
+ * @param wallpaper bitmap containing the wallpaper to use
+ * @param isNightMode whether to use night mode or not
+ *
+ * @return map of system color names to their dynamic values
+ */
+ @VisibleForTesting
+ static Map<String, Integer> createDynamicColorMap(Bitmap wallpaper, boolean isNightMode) {
+ if (wallpaper == null) {
+ return null;
+ }
+ WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(wallpaper);
+ int seed = ColorScheme.getSeedColor(wallpaperColors);
+ ColorScheme scheme = new ColorScheme(seed, isNightMode);
+ Map<String, Integer> dynamicColorMap = new HashMap<>();
+ int paletteSize = scheme.getAccent1().size();
+ extractPalette(scheme.getAllAccentColors(), "accent", paletteSize, dynamicColorMap);
+ extractPalette(scheme.getAllNeutralColors(), "neutral", paletteSize, dynamicColorMap);
+ return dynamicColorMap;
+ }
+
+ /**
+ * Builds the dynamic theme from the {@link ColorScheme} copying what is done
+ * in {@link ThemeOverlayController#getOverlay}
+ */
+ private static void extractPalette(List<Integer> shades, String name, int paletteSize,
+ Map<String, Integer> colorMap) {
+ for (int i = 0; i < shades.size(); i++) {
+ int luminosity = i % paletteSize;
+ int paletteIndex = i / paletteSize + 1;
+ String resourceName;
+ String baseResourceName = "system_" + name + paletteIndex;
+ switch (luminosity) {
+ case 0:
+ resourceName = baseResourceName + "_0";
+ colorMap.put(resourceName, Color.BLACK);
+ resourceName = baseResourceName + "_10";
+ break;
+ case 1:
+ resourceName = baseResourceName + "_50";
+ break;
+ default:
+ resourceName = baseResourceName + "_" + (luminosity - 1) + "00";
+ }
+ colorMap.put(resourceName, ColorUtils.setAlphaComponent(shades.get(i), 0xFF));
+ }
+ }
+
+ private static boolean isDynamicColor(ResourceValue resourceValue) {
+ if (!resourceValue.isFramework() || resourceValue.getResourceType() != ResourceType.COLOR) {
+ return false;
+ }
+ return resourceValue.getName().startsWith("system_accent")
+ || resourceValue.getName().startsWith("system_neutral");
+ }
+
+ public boolean hasDynamicColors() {
+ return mDynamicColorMap != null;
+ }
+
+ @VisibleForTesting
+ void setDynamicColorMap(Map<String, Integer> dynamicColorMap) {
+ mDynamicColorMap = dynamicColorMap;
+ }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
index 678b244893..08fb9caa26 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -70,6 +70,13 @@ public final class RenderParamsFlags {
public static final Key<Boolean> FLAG_ENABLE_LAYOUT_VALIDATOR_IMAGE_CHECK =
new Key<>("enableLayoutValidatorImageCheck", Boolean.class);
+ /**
+ * To tell Layoutlib the path of the image file of the wallpaper to use for dynamic theming.
+ * If null, use default system colors.
+ */
+ public static final Key<String> FLAG_KEY_WALLPAPER_PATH =
+ new Key<>("wallpaperPath", String.class);
+
// Disallow instances.
private RenderParamsFlags() {}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 2b87435669..31cc1e18ee 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -261,6 +261,7 @@ public abstract class RenderAction<T extends RenderParams> {
// scene
mContext.initResources(mParams.getAssets());
sCurrentContext = mContext;
+ mContext.applyWallpaper(mParams.getFlag(RenderParamsFlags.FLAG_KEY_WALLPAPER_PATH));
// Set-up WindowManager
// FIXME: find those out, and possibly add them to the render params
diff --git a/bridge/tests/res/com/android/layoutlib/testdata/wallpaper1.webp b/bridge/tests/res/com/android/layoutlib/testdata/wallpaper1.webp
new file mode 100644
index 0000000000..6460b17b01
--- /dev/null
+++ b/bridge/tests/res/com/android/layoutlib/testdata/wallpaper1.webp
Binary files differ
diff --git a/bridge/tests/res/com/android/layoutlib/testdata/wallpaper2.webp b/bridge/tests/res/com/android/layoutlib/testdata/wallpaper2.webp
new file mode 100644
index 0000000000..786712d078
--- /dev/null
+++ b/bridge/tests/res/com/android/layoutlib/testdata/wallpaper2.webp
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png
new file mode 100644
index 0000000000..61f1f18af1
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png
new file mode 100644
index 0000000000..dd1dd57027
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
index cc9c449563..80f760a278 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
@@ -3,4 +3,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/red" />
<foreground android:drawable="@drawable/headset" />
+ <monochrome android:drawable="@drawable/headset" />
</adaptive-icon> \ No newline at end of file
diff --git a/bridge/tests/run_tests.sh b/bridge/tests/run_tests.sh
index ae737fbe20..e25d272591 100755
--- a/bridge/tests/run_tests.sh
+++ b/bridge/tests/run_tests.sh
@@ -50,7 +50,7 @@ ${STUDIO_JDK}/bin/java -ea \
-Dplatform.dir=${PLATFORM} \
-Dtest_res.dir=${SCRIPT_DIR}/res \
-Dtest_failure.dir=${OUT_DIR}/${FAILURE_DIR} \
- -cp ${MISC_COMMON}/tools-common/tools-common-prebuilt.jar:${MISC_COMMON}/ninepatch/ninepatch-prebuilt.jar:${MISC_COMMON}/sdk-common/sdk-common.jar:${MISC_COMMON}/kxml2/kxml2-2.3.0.jar:${MISC_COMMON}/layoutlib_api/layoutlib_api-prebuilt.jar:${OUT_INTERMEDIATES}/prebuilts/tools/common/m2/trove-prebuilt/linux_glibc_common/combined/trove-prebuilt.jar:${OUT_INTERMEDIATES}/external/junit/junit/linux_glibc_common/javac/junit.jar:${OUT_INTERMEDIATES}/external/guava/guava-jre/linux_glibc_common/javac/guava-jre.jar:${OUT_INTERMEDIATES}/external/hamcrest/hamcrest-core/hamcrest/linux_glibc_common/javac/hamcrest.jar:${OUT_INTERMEDIATES}/external/mockito/mockito/linux_glibc_common/combined/mockito.jar:${OUT_INTERMEDIATES}/external/objenesis/objenesis/linux_glibc_common/javac/objenesis.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/bridge/layoutlib/linux_glibc_common/withres/layoutlib.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/temp_layoutlib/linux_glibc_common/gen/temp_layoutlib.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/bridge/tests/layoutlib-tests/linux_glibc_common/withres/layoutlib-tests.jar \
+ -cp ${MISC_COMMON}/tools-common/tools-common-prebuilt.jar:${MISC_COMMON}/ninepatch/ninepatch-prebuilt.jar:${MISC_COMMON}/sdk-common/sdk-common.jar:${MISC_COMMON}/kxml2/kxml2-2.3.0.jar:${MISC_COMMON}/layoutlib_api/layoutlib_api-prebuilt.jar:${OUT_INTERMEDIATES}/prebuilts/tools/common/m2/trove-prebuilt/linux_glibc_common/combined/trove-prebuilt.jar:${OUT_INTERMEDIATES}/external/junit/junit/linux_glibc_common/javac/junit.jar:${OUT_INTERMEDIATES}/external/guava/guava-jre/linux_glibc_common/javac/guava-jre.jar:${OUT_INTERMEDIATES}/external/hamcrest/hamcrest-core/hamcrest/linux_glibc_common/javac/hamcrest.jar:${OUT_INTERMEDIATES}/external/mockito/mockito/linux_glibc_common/combined/mockito.jar:${OUT_INTERMEDIATES}/external/objenesis/objenesis/linux_glibc_common/javac/objenesis.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/bridge/layoutlib/linux_glibc_common/withres/layoutlib.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/temp_layoutlib/linux_glibc_common/gen/temp_layoutlib.jar:${OUT_INTERMEDIATES}/frameworks/layoutlib/bridge/tests/layoutlib-tests/linux_glibc_common/withres/layoutlib-tests.jar:${OUT_INTERMEDIATES}/external/kotlinc/kotlin-stdlib/linux_glibc_common/combined/kotlin-stdlib.jar \
org.junit.runner.JUnitCore \
com.android.layoutlib.bridge.intensive.Main
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java
index 5266c57909..fd962c1c82 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.RenderAction;
@@ -32,9 +33,14 @@ import android.R.style;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.util.DisplayMetrics;
import android.view.ContextThemeWrapper;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -148,4 +154,47 @@ public class BridgeContextTest extends RenderTestBase {
assertNull(context.getSystemService("my_custom_service"));
sRenderMessages.removeIf(message -> message.equals("Service my_custom_service was not found or is unsupported"));
}
+
+ @Test
+ public void dynamicTheming() throws ClassNotFoundException {
+ LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material", false)
+ .build();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+ context.initResources(params.getAssets());
+ try {
+ assertEquals(-13749965, context.getResources().getColor(android.R.color.system_neutral1_800, null));
+
+ Bitmap wallpaper = BitmapFactory.decodeStream(
+ getClass().getResourceAsStream(
+ "/com/android/layoutlib/testdata/wallpaper1.webp"
+ ));
+ Map<String, Integer> dynamicColorMap =
+ DynamicRenderResources.createDynamicColorMap(wallpaper,
+ configuration.isNightModeActive());
+ ((DynamicRenderResources)context.getRenderResources()).setDynamicColorMap(dynamicColorMap);
+ assertEquals(-13226195, context.getResources().getColor(android.R.color.system_neutral1_800, null));
+
+ wallpaper = BitmapFactory.decodeStream(
+ getClass().getResourceAsStream(
+ "/com/android/layoutlib/testdata/wallpaper2.webp"
+ ));
+ dynamicColorMap = DynamicRenderResources.createDynamicColorMap(wallpaper,
+ configuration.isNightModeActive());
+ ((DynamicRenderResources)context.getRenderResources()).setDynamicColorMap(dynamicColorMap);
+ assertEquals(-13749969, context.getResources().getColor(android.R.color.system_neutral1_800, null));
+ } finally {
+ context.disposeResources();
+ }
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java
new file mode 100644
index 0000000000..0f63a12790
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.intensive.RenderTestBase;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class DynamicRenderResourcesTest extends RenderTestBase {
+ @BeforeClass
+ public static void setUp() {
+ Bridge.prepareThread();
+ }
+
+ @Test
+ public void createDynamicTheme() {
+ Bitmap wallpaper = BitmapFactory.decodeStream(
+ getClass().getResourceAsStream(
+ "/com/android/layoutlib/testdata/wallpaper1.webp"
+ ));
+ Map<String, Integer> dynamicColorMap =
+ DynamicRenderResources.createDynamicColorMap(wallpaper, false);
+ assertNotNull(dynamicColorMap);
+ assertEquals(-16777216, (int)dynamicColorMap.get("system_accent1_0"));
+ assertEquals(-4632, (int)dynamicColorMap.get("system_accent1_50"));
+ assertEquals(-1403268, (int)dynamicColorMap.get("system_accent1_300"));
+ assertEquals(-11198451, (int)dynamicColorMap.get("system_accent1_800"));
+ assertEquals(-16777216, (int)dynamicColorMap.get("system_accent2_0"));
+ assertEquals(-4632, (int)dynamicColorMap.get("system_accent2_50"));
+ assertEquals(-3497321, (int)dynamicColorMap.get("system_accent2_300"));
+ assertEquals(-12309982, (int)dynamicColorMap.get("system_accent2_800"));
+ assertEquals(-16777216, (int)dynamicColorMap.get("system_accent3_0"));
+ assertEquals(-3900, (int)dynamicColorMap.get("system_accent3_50"));
+ assertEquals(-4478092, (int)dynamicColorMap.get("system_accent3_300"));
+ assertEquals(-12963835, (int)dynamicColorMap.get("system_accent3_800"));
+ assertEquals(-16777216, (int)dynamicColorMap.get("system_neutral1_0"));
+ assertEquals(-266518, (int)dynamicColorMap.get("system_neutral1_50"));
+ assertEquals(-4937306, (int)dynamicColorMap.get("system_neutral1_300"));
+ assertEquals(-13226195, (int)dynamicColorMap.get("system_neutral1_800"));
+ assertEquals(-16777216, (int)dynamicColorMap.get("system_neutral2_0"));
+ assertEquals(-4632, (int)dynamicColorMap.get("system_neutral2_50"));
+ assertEquals(-4413535, (int)dynamicColorMap.get("system_neutral2_300"));
+ assertEquals(-12899031, (int)dynamicColorMap.get("system_neutral2_800"));
+ }
+}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index 7cbf777956..ead08aeff0 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -28,6 +28,7 @@ import com.android.ide.common.rendering.api.XmlParserFactory;
import com.android.internal.R;
import com.android.internal.lang.System_Delegate;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.DynamicRenderResources;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.RenderAction;
@@ -54,6 +55,8 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources_Delegate;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.util.StateSet;
@@ -67,10 +70,15 @@ import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
+import java.nio.file.StandardCopyOption;
import java.util.concurrent.TimeUnit;
+import com.google.common.io.Files;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -2011,4 +2019,69 @@ public class RenderTests extends RenderTestBase {
renderAndVerify(params, "window_background.png",
TimeUnit.SECONDS.toNanos(2));
}
+
+ @Test
+ public void testThemedAdaptiveIcon() throws ClassNotFoundException, IOException {
+ // Create the layout pull parser.
+ String layout =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"fill_parent\"\n" +
+ " android:layout_height=\"fill_parent\">\n" +
+ " <ImageView\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:src=\"@drawable/adaptive\" />\n" +
+ "</LinearLayout>\n";
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
+ params.setFlag(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH,
+ "M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 " +
+ "22.4 22.4 0 50 0Z");
+ renderAndVerify(params, "adaptive_icon_circle.png");
+
+ File w1 = File.createTempFile("wallpaper1", ".webp");
+ try (InputStream inputStream = getClass().getResourceAsStream(
+ "/com/android/layoutlib/testdata/wallpaper1.webp")) {
+ java.nio.file.Files.copy(inputStream, w1.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
+ params.setFlag(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH,
+ "M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 " +
+ "22.4 22.4 0 50 0Z");
+ params.setFlag(RenderParamsFlags.FLAG_KEY_WALLPAPER_PATH, w1.getPath());
+ renderAndVerify(params, "adaptive_icon_dynamic_orange.png");
+
+ File w2 = File.createTempFile("wallpaper2", ".webp");
+ try (InputStream inputStream = getClass().getResourceAsStream(
+ "/com/android/layoutlib/testdata/wallpaper2.webp")) {
+ java.nio.file.Files.copy(inputStream, w2.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
+ params.setFlag(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH,
+ "M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 " +
+ "22.4 22.4 0 50 0Z");
+ params.setFlag(RenderParamsFlags.FLAG_KEY_WALLPAPER_PATH, w2.getPath());
+ renderAndVerify(params, "adaptive_icon_dynamic_green.png");
+ }
}
diff --git a/common/src/com/android/tools/layoutlib/create/NativeConfig.java b/common/src/com/android/tools/layoutlib/create/NativeConfig.java
index 41df7c97f9..70146428e7 100644
--- a/common/src/com/android/tools/layoutlib/create/NativeConfig.java
+++ b/common/src/com/android/tools/layoutlib/create/NativeConfig.java
@@ -87,6 +87,7 @@ public class NativeConfig {
"android.graphics.Canvas#getClipBounds",
"android.graphics.ImageDecoder#decodeBitmapImpl",
"android.graphics.Typeface#create",
+ "android.graphics.drawable.AdaptiveIconDrawable#draw",
"android.graphics.drawable.AnimatedVectorDrawable#draw",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorUI#onDraw",
"android.graphics.drawable.DrawableInflater#inflateFromClass",
diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e4c6993d43..1badaf60a7 100644
--- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -300,6 +300,7 @@ public final class CreateInfo implements ICreateInfo {
new String[] {
"android.preference.PreferenceActivity",
"java.**",
+ "kotlin.**",
"org.kxml2.io.KXmlParser",
"org.xmlpull.**",
"sun.**",
diff --git a/create/src/com/android/tools/layoutlib/create/Main.java b/create/src/com/android/tools/layoutlib/create/Main.java
index a32efafdb6..2de360b688 100644
--- a/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/create/src/com/android/tools/layoutlib/create/Main.java
@@ -132,6 +132,7 @@ public class Main {
"com.android.internal.util.*",
"com.android.internal.view.menu.ActionMenu",
"com.android.internal.widget.*",
+ "com.android.systemui.monet.*", // needed for dynamic theming
"com.google.android.apps.common.testing.accessibility.**",
"com.google.android.libraries.accessibility.**",
"libcore.icu.ICU", // needed by ICU_Delegate in LayoutLib