1 /* 2 * Copyright (C) 2021 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.managedprovisioning.common; 18 19 import static java.util.Objects.requireNonNull; 20 21 import androidx.annotation.ColorRes; 22 import androidx.appcompat.app.AppCompatDelegate; 23 24 import com.android.managedprovisioning.R; 25 26 import java.util.HashMap; 27 import java.util.Map; 28 29 /** 30 * Helper which handles animation dynamic colors. 31 * 32 * <p>Animations adapt according to the system theme. The colors of specific layers are updated 33 * according to a color map. 34 * 35 * <p>{@link AnimationDynamicColorsHelper#setupAnimationDynamicColors( 36 * AnimationDynamicColorsHelper.AnimationWrapper, int)} does the following: 37 * <ol> 38 * <li>Iterates through a predefined map of 39 * light-dark mode layer names and colors (or vice-versa if device is in dark-mode) 40 * and sets each layer to the appropriate color</li> 41 * <li>Not all colors map bi-directionally, some colors are asymmetric 42 * as several light colors can map to a single dark color. For these colors the method 43 * iterates through a predefined map of light->dark mode layer names, and <i>only</i> 44 * sets the light mode layers to the appropriate dark color. If the system is in light 45 * mode, the method does nothing for this case.</li> 46 * </ol> 47 * 48 */ 49 public class AnimationDynamicColorsHelper { 50 private static final HomogenousBiMap<ColorItem> sLightToDarkColorsBiMap = 51 buildLightToDarkColorsBiMap(); 52 53 /** 54 * A map which only maps colors from light to dark mode. 55 * 56 * <p>This is necessary in cases when multiple colors in light mode map to a single color to 57 * dark mode. 58 */ 59 private static final Map<ColorItem, ColorItem> sLightToDarkOnly = buildLightToDarkColorsMap(); 60 61 /** 62 * A wrapper over dynamically-colored animations. 63 */ 64 public interface AnimationWrapper { 65 66 /** 67 * Loads the animation asynchronously. Must invoke {@code callback} when done. 68 */ loadAnimation(Runnable callback)69 void loadAnimation(Runnable callback); 70 /** 71 * Sets this animation's {@code layerName} layer to color with resource id 72 * {@code targetColorRes}. 73 */ setLayerColor(String layerName, @ColorRes int targetColorRes)74 void setLayerColor(String layerName, @ColorRes int targetColorRes); 75 76 } 77 /** 78 * Updates the relevant animation with theme-specific colors. 79 * 80 * <p>The algorithm is: 81 * <ol> 82 * <li>Iterates through a predefined map of 83 * light-dark mode layer names and colors (or vice-versa if device is in dark-mode) 84 * and sets each layer to the appropriate color</li> 85 * <li>Not all colors map bi-directionally, some colors are asymmetric 86 * as several light colors can map to a single dark color. For these colors the method 87 * iterates through a predefined map of light->dark mode layer names, and <i>only</i> 88 * sets the light mode layers to the appropriate dark color. If the system is in light 89 * mode, the method does nothing for this case.</li> 90 * </ol> 91 * 92 * @param animationWrapper - a wrapper over the animation that supports dynamic colors 93 * @param nightMode - {@link AppCompatDelegate#MODE_NIGHT_NO} or {@link 94 * AppCompatDelegate#MODE_NIGHT_YES} 95 */ setupAnimationDynamicColors(AnimationWrapper animationWrapper, int nightMode)96 public void setupAnimationDynamicColors(AnimationWrapper animationWrapper, int nightMode) { 97 animationWrapper.loadAnimation( 98 () -> setupAnimationDynamicColorsInternal(animationWrapper, nightMode)); 99 } 100 setupAnimationDynamicColorsInternal( AnimationWrapper animationWrapper, int nightMode)101 private void setupAnimationDynamicColorsInternal( 102 AnimationWrapper animationWrapper, int nightMode) { 103 setupSymmetricLayerColors(animationWrapper, nightMode); 104 setupLightToDarkLayerColors(animationWrapper, nightMode); 105 } 106 setupLightToDarkLayerColors(AnimationWrapper animationWrapper, int nightMode)107 private void setupLightToDarkLayerColors(AnimationWrapper animationWrapper, int nightMode) { 108 if (nightMode == AppCompatDelegate.MODE_NIGHT_NO) { 109 return; 110 } 111 for (ColorItem lightColorItem : sLightToDarkOnly.keySet()) { 112 ColorItem darkColorItem = sLightToDarkOnly.get(lightColorItem); 113 animationWrapper.setLayerColor(lightColorItem.mLayerName, darkColorItem.mColorRes); 114 } 115 } 116 setupSymmetricLayerColors(AnimationWrapper animationWrapper, int nightMode)117 private void setupSymmetricLayerColors(AnimationWrapper animationWrapper, int nightMode) { 118 Map<ColorItem, ColorItem> colorMap = getColorMap(nightMode); 119 for (ColorItem targetColorItem : colorMap.keySet()) { 120 ColorItem otherColorItem = colorMap.get(targetColorItem); 121 animationWrapper.setLayerColor(otherColorItem.mLayerName, targetColorItem.mColorRes); 122 } 123 } 124 getColorMap(int nightMode)125 private Map<ColorItem, ColorItem> getColorMap(int nightMode) { 126 Map<ColorItem, ColorItem> map; 127 if (isDayMode(nightMode)) { 128 map = sLightToDarkColorsBiMap.getForwardMap(); 129 } else { 130 map = sLightToDarkColorsBiMap.getReverseMap(); 131 } 132 return map; 133 } 134 isDayMode(int nightMode)135 private boolean isDayMode(int nightMode) { 136 return nightMode == AppCompatDelegate.MODE_NIGHT_NO; 137 } 138 139 private static class ColorItem { 140 141 private final int mColorRes; 142 private final String mLayerName; ColorItem(int colorRes, String layerName)143 ColorItem(int colorRes, String layerName) { 144 this.mColorRes = colorRes; 145 this.mLayerName = requireNonNull(layerName); 146 } 147 148 } 149 buildLightToDarkColorsBiMap()150 private static HomogenousBiMap<ColorItem> buildLightToDarkColorsBiMap() { 151 HomogenousBiMap<ColorItem> result = new HomogenousBiMap<>(); 152 result.put( 153 new ColorItem(R.color.blue600, ".blue600"), 154 new ColorItem(R.color.blue400, ".blue400")); 155 result.put( 156 new ColorItem(R.color.green600, ".green600"), 157 new ColorItem(R.color.green400, ".green400")); 158 result.put( 159 new ColorItem(R.color.red600, ".red600"), 160 new ColorItem(R.color.red400, ".red400")); 161 result.put( 162 new ColorItem(R.color.yellow600, ".yellow600"), 163 new ColorItem(R.color.yellow400, ".yellow400")); 164 result.put( 165 new ColorItem(R.color.blue400, ".blue400"), 166 new ColorItem(R.color.blue100, ".blue100")); 167 result.put( 168 new ColorItem(R.color.green400, ".green400"), 169 new ColorItem(R.color.green100, ".green100")); 170 result.put( 171 new ColorItem(R.color.red400, ".red400"), 172 new ColorItem(R.color.red100, ".red100")); 173 result.put( 174 new ColorItem(R.color.yellow400, ".yellow400"), 175 new ColorItem(R.color.yellow100, ".yellow100")); 176 result.put( 177 new ColorItem(R.color.blue300, ".blue300"), 178 new ColorItem(R.color.blue800, ".blue800")); 179 result.put( 180 new ColorItem(R.color.grey300, ".grey300"), 181 new ColorItem(R.color.grey600, ".grey600")); 182 result.put( 183 new ColorItem(R.color.grey400, ".grey400"), 184 new ColorItem(R.color.grey700, ".grey700")); 185 result.put( 186 new ColorItem(R.color.grey200, ".grey200"), 187 new ColorItem(R.color.grey800, ".grey800")); 188 result.put( 189 new ColorItem(R.color.white, ".white"), 190 new ColorItem(R.color.black, ".black")); 191 result.put( 192 new ColorItem(R.color.orange600, ".orange600"), 193 new ColorItem(R.color.orange300, ".orange300")); 194 result.put( 195 new ColorItem(R.color.pink600, ".pink600"), 196 new ColorItem(R.color.pink300, ".pink300")); 197 result.put( 198 new ColorItem(R.color.purple600, ".purple600"), 199 new ColorItem(R.color.purple300, ".purple300")); 200 result.put( 201 new ColorItem(R.color.cyan600, ".cyan600"), 202 new ColorItem(R.color.cyan300, ".cyan300")); 203 result.put( 204 new ColorItem(R.color.orange400, ".orange400"), 205 new ColorItem(R.color.orange100, ".orange100")); 206 result.put( 207 new ColorItem(R.color.pink400, ".pink400"), 208 new ColorItem(R.color.pink100, ".pink100")); 209 result.put( 210 new ColorItem(R.color.cyan400, ".cyan400"), 211 new ColorItem(R.color.cyan100, ".cyan100")); 212 return result; 213 } 214 buildLightToDarkColorsMap()215 private static Map<ColorItem, ColorItem> buildLightToDarkColorsMap() { 216 Map<ColorItem, ColorItem> result = new HashMap<>(); 217 result.put( 218 new ColorItem(R.color.blue50, ".blue50"), 219 new ColorItem(R.color.grey900, ".grey900")); 220 result.put( 221 new ColorItem(R.color.green50, ".green50"), 222 new ColorItem(R.color.grey900, ".grey900")); 223 result.put( 224 new ColorItem(R.color.red50, ".red50"), 225 new ColorItem(R.color.grey900, ".grey900")); 226 result.put( 227 new ColorItem(R.color.yellow50, ".yellow50"), 228 new ColorItem(R.color.grey900, ".grey900")); 229 result.put( 230 new ColorItem(R.color.purple400, ".purple400"), 231 new ColorItem(R.color.purple800, ".purple800")); 232 result.put( 233 new ColorItem(R.color.purple300, ".purple300"), 234 new ColorItem(R.color.purple800, ".purple800")); 235 return result; 236 } 237 } 238