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 android.content.res.Configuration.UI_MODE_NIGHT_MASK; 20 import static android.content.res.Configuration.UI_MODE_NIGHT_YES; 21 22 import static com.android.managedprovisioning.provisioning.Constants.FLAG_ENABLE_LIGHT_DARK_MODE; 23 24 import static com.google.android.setupdesign.util.ThemeHelper.trySetDynamicColor; 25 26 import static java.util.Objects.requireNonNull; 27 28 import android.content.Context; 29 import android.content.Intent; 30 import android.os.SystemProperties; 31 import android.text.TextUtils; 32 import android.webkit.WebSettings; 33 34 import androidx.appcompat.app.AppCompatDelegate; 35 import androidx.webkit.WebSettingsCompat; 36 import androidx.webkit.WebViewFeature; 37 38 import com.airbnb.lottie.LottieAnimationView; 39 import com.airbnb.lottie.LottieComposition; 40 import com.google.android.setupcompat.util.WizardManagerHelper; 41 import com.google.android.setupdesign.R; 42 import com.google.android.setupdesign.util.ThemeResolver; 43 import static com.google.android.setupdesign.util.ThemeHelper.trySetSuwTheme; 44 /** 45 * Helper with utility methods to manage the ManagedProvisioning theme and night mode. 46 */ 47 public class ThemeHelper { 48 private static final String SYSTEM_PROPERTY_SETUPWIZARD_THEME = 49 SystemProperties.get("setupwizard.theme"); 50 51 private final NightModeChecker mNightModeChecker; 52 private final SetupWizardBridge mSetupWizardBridge; 53 private final AnimationDynamicColorsHelper mAnimationDynamicColorsHelper; 54 ThemeHelper(NightModeChecker nightModeChecker, SetupWizardBridge setupWizardBridge)55 public ThemeHelper(NightModeChecker nightModeChecker, SetupWizardBridge setupWizardBridge) { 56 mNightModeChecker = requireNonNull(nightModeChecker); 57 mSetupWizardBridge = requireNonNull(setupWizardBridge); 58 // TODO(b/190182035): Tidy up tests after adding dependency injection support 59 mAnimationDynamicColorsHelper = new AnimationDynamicColorsHelper(); 60 } 61 62 /** 63 * Infers the correct theme resource id. 64 */ inferThemeResId(Context context, Intent intent)65 public int inferThemeResId(Context context, Intent intent) { 66 requireNonNull(context); 67 requireNonNull(intent); 68 String themeName = getDefaultThemeName(context, intent); 69 int defaultTheme = mSetupWizardBridge.isSetupWizardDayNightEnabled(context) 70 ? R.style.SudThemeGlifV4_DayNight 71 : R.style.SudThemeGlifV4_Light; 72 return mSetupWizardBridge 73 .resolveTheme(defaultTheme, themeName, shouldSuppressDayNight(context)); 74 } 75 76 /** Returns {@code true} if the SUW theme is set. */ setSuwTheme(Context context)77 public boolean setSuwTheme(Context context) { 78 requireNonNull(context); 79 return trySetSuwTheme(context); 80 } 81 /** 82 * Sets up theme-specific colors. Must be called after {@link 83 * #inferThemeResId(Context, Intent)}. 84 */ setupDynamicColors(Context context)85 public void setupDynamicColors(Context context) { 86 requireNonNull(context); 87 trySetDynamicColor(context); 88 } 89 90 /** Returns {@code true} if this {@code context} should applied Glif expressive style. */ shouldApplyGlifExpressiveStyle(Context context)91 public static boolean shouldApplyGlifExpressiveStyle(Context context) { 92 requireNonNull(context); 93 return 94 com.google.android.setupdesign.util.ThemeHelper.shouldApplyGlifExpressiveStyle(context); 95 } 96 97 /** 98 * Returns the appropriate day or night mode, depending on the setup wizard flags. 99 * 100 * @return {@link AppCompatDelegate#MODE_NIGHT_YES} or {@link AppCompatDelegate#MODE_NIGHT_NO} 101 */ getDefaultNightMode(Context context, Intent intent)102 public int getDefaultNightMode(Context context, Intent intent) { 103 requireNonNull(context); 104 if (TextUtils.isEmpty(getProvidedTheme(intent))) { 105 return isSystemNightMode(context) 106 ? AppCompatDelegate.MODE_NIGHT_YES 107 : AppCompatDelegate.MODE_NIGHT_NO; 108 } 109 if (shouldSuppressDayNight(context)) { 110 return AppCompatDelegate.MODE_NIGHT_NO; 111 } 112 if (isSystemNightMode(context)) { 113 return AppCompatDelegate.MODE_NIGHT_YES; 114 } 115 return AppCompatDelegate.MODE_NIGHT_NO; 116 } 117 118 /** 119 * Forces the web pages shown by the {@link android.webkit.WebView} which has the 120 * supplied {@code webSettings} to have the appropriate day/night mode depending 121 * on the app theme. 122 */ applyWebSettingsDayNight(Context context, WebSettings webSettings, Intent intent)123 public void applyWebSettingsDayNight(Context context, WebSettings webSettings, Intent intent) { 124 requireNonNull(context); 125 requireNonNull(webSettings); 126 if (!WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { 127 return; 128 } 129 WebSettingsCompat.setForceDark(webSettings, getForceDarkMode(context, intent)); 130 } 131 132 /** 133 * Updates the relevant animation with theme-specific colors. 134 * <p>If the supplied {@link LottieAnimationView} does not have a loaded {@link 135 * LottieComposition}, it asynchronously waits for it to load and then applies the colors. 136 */ setupAnimationDynamicColors( Context context, LottieAnimationView lottieAnimationView, Intent intent)137 public void setupAnimationDynamicColors( 138 Context context, LottieAnimationView lottieAnimationView, Intent intent) { 139 mAnimationDynamicColorsHelper.setupAnimationDynamicColors( 140 new LottieAnimationWrapper(lottieAnimationView), 141 getDefaultNightMode(context, intent)); 142 } 143 getForceDarkMode(Context context, Intent intent)144 private int getForceDarkMode(Context context, Intent intent) { 145 if (getDefaultNightMode(context, intent) == AppCompatDelegate.MODE_NIGHT_YES) { 146 return WebSettingsCompat.FORCE_DARK_ON; 147 } else { 148 return WebSettingsCompat.FORCE_DARK_OFF; 149 } 150 } 151 shouldSuppressDayNight(Context context)152 private boolean shouldSuppressDayNight(Context context) { 153 if (!FLAG_ENABLE_LIGHT_DARK_MODE) { 154 return true; 155 } 156 return !mSetupWizardBridge.isSetupWizardDayNightEnabled(context); 157 } 158 isSystemNightMode(Context context)159 private boolean isSystemNightMode(Context context) { 160 return mNightModeChecker.isSystemNightMode(context); 161 } 162 getDefaultThemeName(Context context, Intent intent)163 private String getDefaultThemeName(Context context, Intent intent) { 164 String theme = getProvidedTheme(intent); 165 if (TextUtils.isEmpty(theme)) { 166 if (isSystemNightMode(context)) { 167 theme = com.google.android.setupdesign.util.ThemeHelper.THEME_GLIF_V4; 168 } else { 169 theme = com.google.android.setupdesign.util.ThemeHelper.THEME_GLIF_V4_LIGHT; 170 } 171 } 172 return theme; 173 } 174 getProvidedTheme(Intent intent)175 private String getProvidedTheme(Intent intent) { 176 String theme = intent.getStringExtra(WizardManagerHelper.EXTRA_THEME); 177 if (TextUtils.isEmpty(theme)) { 178 return mSetupWizardBridge.getSystemPropertySetupWizardTheme(); 179 } 180 return theme; 181 } 182 183 interface SetupWizardBridge { isSetupWizardDayNightEnabled(Context context)184 boolean isSetupWizardDayNightEnabled(Context context); 185 getSystemPropertySetupWizardTheme()186 String getSystemPropertySetupWizardTheme(); 187 resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight)188 int resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight); 189 } 190 191 interface NightModeChecker { isSystemNightMode(Context context)192 boolean isSystemNightMode(Context context); 193 } 194 195 /** 196 * Default implementation of {@link NightModeChecker}. 197 */ 198 public static class DefaultNightModeChecker implements NightModeChecker { 199 @Override isSystemNightMode(Context context)200 public boolean isSystemNightMode(Context context) { 201 return (context.getResources().getConfiguration().uiMode & UI_MODE_NIGHT_MASK) 202 == UI_MODE_NIGHT_YES; 203 } 204 } 205 206 /** 207 * Default implementation of {@link SetupWizardBridge}. 208 */ 209 public static class DefaultSetupWizardBridge implements SetupWizardBridge { 210 @Override isSetupWizardDayNightEnabled(Context context)211 public boolean isSetupWizardDayNightEnabled(Context context) { 212 return com.google.android.setupdesign.util.ThemeHelper 213 .isSetupWizardDayNightEnabled(context); 214 } 215 216 @Override getSystemPropertySetupWizardTheme()217 public String getSystemPropertySetupWizardTheme() { 218 return SYSTEM_PROPERTY_SETUPWIZARD_THEME; 219 } 220 221 @Override resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight)222 public int resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight) { 223 ThemeResolver themeResolver = new ThemeResolver.Builder(ThemeResolver.getDefault()) 224 .setDefaultTheme(defaultTheme) 225 .setUseDayNight(true) 226 .build(); 227 return themeResolver.resolve( 228 themeName, 229 suppressDayNight); 230 } 231 } 232 } 233