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