diff --git a/lib/java/com/google/android/material/color/ColorResourcesLoaderCreator.java b/lib/java/com/google/android/material/color/ColorResourcesLoaderCreator.java new file mode 100644 index 00000000000..3d1727e4f27 --- /dev/null +++ b/lib/java/com/google/android/material/color/ColorResourcesLoaderCreator.java @@ -0,0 +1,75 @@ +/* + * 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.google.android.material.color; + +import android.content.Context; +import android.content.res.loader.ResourcesLoader; +import android.content.res.loader.ResourcesProvider; +import android.os.Build.VERSION_CODES; +import android.os.ParcelFileDescriptor; +import android.system.Os; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Map; + +/** This class creates a Resources Table at runtime and helps replace color Resources on the fly. */ +@RequiresApi(VERSION_CODES.R) +final class ColorResourcesLoaderCreator { + + private ColorResourcesLoaderCreator() {} + + private static final String TAG = ColorResourcesLoaderCreator.class.getSimpleName(); + + @Nullable + static ResourcesLoader create( + @NonNull Context context, @NonNull Map colorMapping) { + try { + byte[] contentBytes = ColorResourcesTableCreator.create(context, colorMapping); + Log.i(TAG, "Table created, length: " + contentBytes.length); + if (contentBytes.length == 0) { + return null; + } + FileDescriptor arscFile = null; + try { + arscFile = Os.memfd_create("temp.arsc", /* flags= */ 0); + // Note: This must not be closed through the OutputStream. + try (OutputStream pipeWriter = new FileOutputStream(arscFile)) { + pipeWriter.write(contentBytes); + + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) { + ResourcesLoader colorsLoader = new ResourcesLoader(); + colorsLoader.addProvider( + ResourcesProvider.loadFromTable(pfd, /* assetsProvider= */ null)); + return colorsLoader; + } + } + } finally { + if (arscFile != null) { + Os.close(arscFile); + } + } + } catch (Exception e) { + Log.e(TAG, "Failed to create the ColorResourcesTableCreator.", e); + } + return null; + } +} diff --git a/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java b/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java index 63f5ba2d18d..2ffaf6ea2d8 100644 --- a/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java +++ b/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java @@ -74,7 +74,7 @@ static byte[] create(Context context, Map colorMapping) throws context.getResources().getResourceName(entry.getKey()), entry.getValue()); if (colorResource.typeId != TYPE_ID_COLOR) { - throw new IllegalArgumentException("Expected color resource not found."); + throw new IllegalArgumentException("Non color resource found: " + colorResource.name); } PackageInfo packageInfo; if (colorResource.packageId == ANDROID_PACKAGE_ID) { diff --git a/lib/java/com/google/android/material/color/DynamicColors.java b/lib/java/com/google/android/material/color/DynamicColors.java index 1d8ff50baf8..a5155e88b30 100644 --- a/lib/java/com/google/android/material/color/DynamicColors.java +++ b/lib/java/com/google/android/material/color/DynamicColors.java @@ -22,15 +22,12 @@ import android.app.Application; import android.app.Application.ActivityLifecycleCallbacks; import android.content.Context; -import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.view.ContextThemeWrapper; -import android.view.View; -import android.view.Window; import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,9 +37,7 @@ import java.util.HashMap; import java.util.Map; -/** - * Utility for applying dynamic colors to application/activities. - */ +/** Utility for applying dynamic colors to application/activities. */ public class DynamicColors { private static final int[] DYNAMIC_COLOR_THEME_OVERLAY_ATTRIBUTE = new int[] { R.attr.dynamicColorThemeOverlay }; @@ -127,12 +122,11 @@ public static void applyToActivitiesIfAvailable(@NonNull Application application } /** - * Applies dynamic colors to all activities with the given theme overlay by registering a - * {@link ActivityLifecycleCallbacks} to your application. + * Applies dynamic colors to all activities with the given theme overlay by registering a {@link + * ActivityLifecycleCallbacks} to your application. * * @see #applyToActivitiesIfAvailable(Application, int, Precondition) for more detailed info and - * examples. - * + * examples. * @param application The target application. * @param theme The resource ID of the theme overlay that provides dynamic color definition. */ @@ -161,8 +155,9 @@ public static void applyToActivitiesIfAvailable( * Applies dynamic colors to all activities with the given theme overlay according to the given * precondition by registering a {@link ActivityLifecycleCallbacks} to your application. * - * A normal usage of this method should happen only once in {@link Application#onCreate()} or any - * methods that run before any of your activities are created. For example: + *

A normal usage of this method should happen only once in {@link Application#onCreate()} or + * any methods that run before any of your activities are created. For example: + * *

    * public class YourApplication extends Application {
    *   @Override
@@ -172,9 +167,10 @@ public static void applyToActivitiesIfAvailable(
    *   }
    * }
    * 
- * This method will try to apply the given dynamic color theme overlay in every activity's - * {@link ActivityLifecycleCallbacks#onActivityPreCreated(Activity, Bundle)} callback. Therefore, - * if you are applying any other theme overlays after that, you will need to be careful about not + * + * This method will try to apply the given dynamic color theme overlay in every activity's {@link + * ActivityLifecycleCallbacks#onActivityPreCreated(Activity, Bundle)} callback. Therefore, if you + * are applying any other theme overlays after that, you will need to be careful about not * overriding the colors or you may lose the dynamic color support. * * @param application The target application. @@ -261,7 +257,7 @@ private static void applyIfAvailable( theme = getDefaultThemeOverlay(activity); } if (theme != 0 && precondition.shouldApplyDynamicColors(activity, theme)) { - applyDynamicColorThemeOverlay(activity, theme); + ThemeUtils.applyThemeOverlay(activity, theme); onAppliedCallback.onApplied(activity); } } @@ -327,34 +323,6 @@ private static int getDefaultThemeOverlay(@NonNull Context context) { return theme; } - private static void applyDynamicColorThemeOverlay(Activity activity, @StyleRes int theme) { - // Use applyStyle() instead of setTheme() due to Force Dark issue. - activity.getTheme().applyStyle(theme, /* force= */ true); - - // Make sure theme is applied to the Window decorView similar to Activity#setTheme, to ensure - // that the dynamic colors will be applied to things like ContextMenu using the DecorContext. - Theme windowDecorViewTheme = getWindowDecorViewTheme(activity); - if (windowDecorViewTheme != null) { - windowDecorViewTheme.applyStyle(theme, /* force= */ true); - } - } - - @Nullable - private static Theme getWindowDecorViewTheme(@NonNull Activity activity) { - Window window = activity.getWindow(); - if (window != null) { - // Use peekDecorView() instead of getDecorView() to avoid locking the Window. - View decorView = window.peekDecorView(); - if (decorView != null) { - Context context = decorView.getContext(); - if (context != null) { - return context.getTheme(); - } - } - } - return null; - } - /** * The interface that provides a precondition to decide if dynamic colors should be applied. */ diff --git a/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java b/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java new file mode 100644 index 00000000000..ce252a134bb --- /dev/null +++ b/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java @@ -0,0 +1,83 @@ +/* + * 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.google.android.material.color; + +import com.google.android.material.R; + +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; + +/** + * A class for specifying color attributes for harmonization, which would contain an int array of + * color attributes, with the option to specify a custom theme overlay. + */ +public final class HarmonizedColorAttributes { + + private final int[] attributes; + @StyleRes private final int themeOverlay; + + private static final int[] HARMONIZED_MATERIAL_ATTRIBUTES = + new int[] { + R.attr.colorError, + R.attr.colorOnError, + R.attr.colorErrorContainer, + R.attr.colorOnErrorContainer + }; + + /** Create HarmonizedColorAttributes with an int array of color attributes. */ + @NonNull + public static HarmonizedColorAttributes create(@NonNull int[] attributes) { + return new HarmonizedColorAttributes(attributes, 0); + } + + /** + * Create HarmonizedColorAttributes with a theme overlay, along with an int array of attributes in + * the theme overlay. + */ + @NonNull + public static HarmonizedColorAttributes create( + @NonNull int[] attributes, @StyleRes int themeOverlay) { + return new HarmonizedColorAttributes(attributes, themeOverlay); + } + + /** Create HarmonizedColorAttributes with Material default, with Error colors being harmonized. */ + @NonNull + public static HarmonizedColorAttributes createMaterialDefaults() { + return create(HARMONIZED_MATERIAL_ATTRIBUTES, R.style.ThemeOverlay_Material3_HarmonizedColors); + } + + private HarmonizedColorAttributes(@NonNull int[] attributes, @StyleRes int themeOverlay) { + if (themeOverlay != 0 && attributes.length == 0) { + throw new IllegalArgumentException( + "Theme overlay should be used with the accompanying int[] attributes."); + } + this.attributes = attributes; + this.themeOverlay = themeOverlay; + } + + /** Returns the int array of color attributes for harmonization. */ + @NonNull + public int[] getAttributes() { + return attributes; + } + + /** Returns the custom theme overlay for harmonization, default is 0 if not specified. */ + @StyleRes + public int getThemeOverlay() { + return themeOverlay; + } +} diff --git a/lib/java/com/google/android/material/color/HarmonizedColors.java b/lib/java/com/google/android/material/color/HarmonizedColors.java new file mode 100644 index 00000000000..71db66c9bf2 --- /dev/null +++ b/lib/java/com/google/android/material/color/HarmonizedColors.java @@ -0,0 +1,168 @@ +/* + * 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.google.android.material.color; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.TypedArray; +import android.content.res.loader.ResourcesLoader; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import androidx.annotation.ChecksSdkIntAtLeast; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; +import java.util.HashMap; +import java.util.Map; + +/** A harmonizer class for harmonizing color resources and attributes. */ +public class HarmonizedColors { + + private HarmonizedColors() {} + + private static final String TAG = HarmonizedColors.class.getSimpleName(); + + /** Harmonizes the specified color resources, attributes, and theme overlay. */ + @NonNull + public static void applyIfAvailable(@NonNull HarmonizedColorsOptions options) { + if (!isHarmonizedColorAvailable()) { + return; + } + Context context = options.getContext(); + int themeOverlay = options.getThemeOverlayResourceId(); + + if (addResourcesLoaderToContext(context, options) && themeOverlay != 0) { + ThemeUtils.applyThemeOverlay(context, themeOverlay); + } + } + + /** + * Wraps the given Context from HarmonizedColorsOptions with the color resources being harmonized. + * + *

If a theme overlay is not provided in HarmonizedColorAttributes, then the context provided + * in HarmonizedColorsOptions will be returned. + */ + @NonNull + public static Context wrapContextIfAvailable(@NonNull HarmonizedColorsOptions options) { + Context context = options.getContext(); + if (!isHarmonizedColorAvailable()) { + return context; + } + int themeOverlay = options.getThemeOverlayResourceId(); + Context newContext = + themeOverlay == 0 + ? new ContextWrapper(context) + : new ContextThemeWrapper(context, themeOverlay); + + return addResourcesLoaderToContext(newContext, options) ? newContext : context; + } + + /** Returns {@code true} if harmonized colors are available on the current SDK level. */ + @ChecksSdkIntAtLeast(api = VERSION_CODES.R) + private static boolean isHarmonizedColorAvailable() { + return VERSION.SDK_INT >= VERSION_CODES.R; + } + + @RequiresApi(api = VERSION_CODES.R) + private static boolean addResourcesLoaderToContext( + Context context, HarmonizedColorsOptions options) { + ResourcesLoader resourcesLoader = + ColorResourcesLoaderCreator.create(context, createHarmonizedColorReplacementMap(options)); + if (resourcesLoader != null) { + context.getResources().addLoaders(resourcesLoader); + return true; + } + return false; + } + + @RequiresApi(api = VERSION_CODES.LOLLIPOP) + private static Map createHarmonizedColorReplacementMap( + HarmonizedColorsOptions options) { + Context context = options.getContext(); + Map colorReplacementMap = new HashMap<>(); + int colorToHarmonizeWith = + MaterialColors.getColor(context, options.getColorAttributeToHarmonizeWith(), TAG); + + // Harmonize color resources. + for (int colorResourceId : options.getColorResourcesIds()) { + int harmonizedColor = + MaterialColors.harmonize( + ContextCompat.getColor(context, colorResourceId), colorToHarmonizeWith); + colorReplacementMap.put(colorResourceId, harmonizedColor); + } + + HarmonizedColorAttributes colorAttributes = options.getColorAttributes(); + if (colorAttributes != null) { + int[] attributes = colorAttributes.getAttributes(); + if (attributes.length > 0) { + // Harmonize theme overlay attributes in the custom theme overlay. If custom theme overlay + // is not provided, look up resources value the theme attributes point to and + // harmonize directly. + int themeOverlay = colorAttributes.getThemeOverlay(); + TypedArray themeAttributesTypedArray = context.obtainStyledAttributes(attributes); + TypedArray themeOverlayAttributesTypedArray = + themeOverlay != 0 + ? new ContextThemeWrapper(context, themeOverlay).obtainStyledAttributes(attributes) + : null; + addHarmonizedColorAttributesToReplacementMap( + colorReplacementMap, + themeAttributesTypedArray, + themeOverlayAttributesTypedArray, + colorToHarmonizeWith); + + themeAttributesTypedArray.recycle(); + if (themeOverlayAttributesTypedArray != null) { + themeOverlayAttributesTypedArray.recycle(); + } + } + } + return colorReplacementMap; + } + + // TypedArray.getType() requires API >= 21. + @RequiresApi(api = VERSION_CODES.LOLLIPOP) + private static void addHarmonizedColorAttributesToReplacementMap( + @NonNull Map colorReplacementMap, + @NonNull TypedArray themeAttributesTypedArray, + @Nullable TypedArray themeOverlayAttributesTypedArray, + @ColorInt int colorToHarmonizeWith) { + TypedArray resourceIdTypedArray = + themeOverlayAttributesTypedArray != null + ? themeOverlayAttributesTypedArray + : themeAttributesTypedArray; + + for (int i = 0; i < themeAttributesTypedArray.getIndexCount(); i++) { + int resourceId = resourceIdTypedArray.getResourceId(i, 0); + if (resourceId != 0 + && themeAttributesTypedArray.hasValue(i) + && isColorResource(themeAttributesTypedArray.getType(i))) { + int colorToHarmonize = themeAttributesTypedArray.getColor(i, 0); + colorReplacementMap.put( + resourceId, MaterialColors.harmonize(colorToHarmonize, colorToHarmonizeWith)); + } + } + } + + private static boolean isColorResource(int attrType) { + return (TypedValue.TYPE_FIRST_COLOR_INT <= attrType) + && (attrType <= TypedValue.TYPE_LAST_COLOR_INT); + } +} diff --git a/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java b/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java new file mode 100644 index 00000000000..84ba6bad163 --- /dev/null +++ b/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java @@ -0,0 +1,126 @@ +/* + * 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.google.android.material.color; + +import com.google.android.material.R; + +import android.content.Context; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; + +/** + * Wrapper class for specifying harmonization options, whether to harmonize an int array of color + * resources, an int array of color attributes and/or harmonize with theme overlay. + */ +public class HarmonizedColorsOptions { + + @NonNull private final Context context; + @NonNull private final int[] colorResourceIds; + @Nullable private final HarmonizedColorAttributes colorAttributes; + @AttrRes private final int colorAttributeToHarmonizeWith; + + /** + * Create HarmonizedColorsOptions with Material default, with Error colors being harmonized with + * Primary. + */ + @NonNull + public static HarmonizedColorsOptions createMaterialDefaults(@NonNull Context context) { + return new HarmonizedColorsOptions.Builder(context) + .setColorAttributes(HarmonizedColorAttributes.createMaterialDefaults()) + .build(); + } + + private HarmonizedColorsOptions(Builder builder) { + this.context = builder.context; + this.colorResourceIds = builder.colorResourceIds; + this.colorAttributes = builder.colorAttributes; + this.colorAttributeToHarmonizeWith = builder.colorAttributeToHarmonizeWith; + } + + /** Returns the {@link Context} for harmonization. */ + @NonNull + public Context getContext() { + return context; + } + + /** Returns the int array of color resources for harmonization. */ + @NonNull + public int[] getColorResourcesIds() { + return colorResourceIds; + } + + /** Returns the color attributes for harmonization. */ + @Nullable + public HarmonizedColorAttributes getColorAttributes() { + return colorAttributes; + } + + /** Returns the color attribute to harmonize with for harmonization. */ + @AttrRes + public int getColorAttributeToHarmonizeWith() { + return colorAttributeToHarmonizeWith; + } + + /** + * Builder class for specifying options when harmonizing colors. When building {@code + * ColorResourceHarmonizerOptions}, a {@link Context} is required. + */ + public static class Builder { + + @NonNull private final Context context; + @NonNull private int[] colorResourceIds = new int[] {}; + @Nullable private HarmonizedColorAttributes colorAttributes; + @AttrRes private int colorAttributeToHarmonizeWith = R.attr.colorPrimary; + + public Builder(@NonNull Context context) { + this.context = context; + } + + /** Sets the int array of color resources ids for harmonization. */ + @NonNull + public Builder setColorResourcesIds(@NonNull int[] colorResourceIds) { + this.colorResourceIds = colorResourceIds; + return this; + } + + /** Sets the harmonized color attributes for harmonization. */ + @Nullable + public Builder setColorAttributes(@Nullable HarmonizedColorAttributes colorAttributes) { + this.colorAttributes = colorAttributes; + return this; + } + + /** Sets the color attribute to harmonize with for harmonization. */ + @NonNull + public Builder setColorAttributeToHarmonizeWith(@AttrRes int colorAttributeToHarmonizeWith) { + this.colorAttributeToHarmonizeWith = colorAttributeToHarmonizeWith; + return this; + } + + @NonNull + public HarmonizedColorsOptions build() { + return new HarmonizedColorsOptions(this); + } + } + + @StyleRes + int getThemeOverlayResourceId() { + return this.colorAttributes != null ? colorAttributes.getThemeOverlay() : 0; + } +} diff --git a/lib/java/com/google/android/material/color/ThemeUtils.java b/lib/java/com/google/android/material/color/ThemeUtils.java new file mode 100644 index 00000000000..f0073f4fbb4 --- /dev/null +++ b/lib/java/com/google/android/material/color/ThemeUtils.java @@ -0,0 +1,62 @@ +/* + * 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.google.android.material.color; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources.Theme; +import android.view.View; +import android.view.Window; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; + +/** Utility methods for theme. */ +final class ThemeUtils { + + private ThemeUtils() {} + + static void applyThemeOverlay(@NonNull Context context, @StyleRes int theme) { + // Use applyStyle() instead of setTheme() due to Force Dark issue. + context.getTheme().applyStyle(theme, /* force= */ true); + + // Make sure the theme overlay is applied to the Window decorView similar to Activity#setTheme, + // to ensure that it will be applied to things like ContextMenu using the DecorContext. + if (context instanceof Activity) { + Theme windowDecorViewTheme = getWindowDecorViewTheme((Activity) context); + if (windowDecorViewTheme != null) { + windowDecorViewTheme.applyStyle(theme, /* force= */ true); + } + } + } + + @Nullable + private static Theme getWindowDecorViewTheme(@NonNull Activity activity) { + Window window = activity.getWindow(); + if (window != null) { + // Use peekDecorView() instead of getDecorView() to avoid locking the Window. + View decorView = window.peekDecorView(); + if (decorView != null) { + Context context = decorView.getContext(); + if (context != null) { + return context.getTheme(); + } + } + } + return null; + } +} diff --git a/lib/java/com/google/android/material/color/res/values/colors.xml b/lib/java/com/google/android/material/color/res/values/colors.xml index 8116879c0b4..777ac94afa6 100644 --- a/lib/java/com/google/android/material/color/res/values/colors.xml +++ b/lib/java/com/google/android/material/color/res/values/colors.xml @@ -53,4 +53,10 @@ #52000000 + + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + diff --git a/lib/java/com/google/android/material/color/res/values/theme_overlay.xml b/lib/java/com/google/android/material/color/res/values/theme_overlay.xml new file mode 100644 index 00000000000..6d072da4704 --- /dev/null +++ b/lib/java/com/google/android/material/color/res/values/theme_overlay.xml @@ -0,0 +1,26 @@ + + + + + + +