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 
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     /**
77      * Sets up theme-specific colors. Must be called after {@link
78      * #inferThemeResId(Context, Intent)}.
79      */
setupDynamicColors(Context context)80     public void setupDynamicColors(Context context) {
81         requireNonNull(context);
82         trySetDynamicColor(context);
83     }
84 
85     /**
86      * Returns the appropriate day or night mode, depending on the setup wizard flags.
87      *
88      * @return {@link AppCompatDelegate#MODE_NIGHT_YES} or {@link AppCompatDelegate#MODE_NIGHT_NO}
89      */
getDefaultNightMode(Context context, Intent intent)90     public int getDefaultNightMode(Context context, Intent intent) {
91         requireNonNull(context);
92         if (TextUtils.isEmpty(getProvidedTheme(intent))) {
93             return isSystemNightMode(context)
94                     ? AppCompatDelegate.MODE_NIGHT_YES
95                     : AppCompatDelegate.MODE_NIGHT_NO;
96         }
97         if (shouldSuppressDayNight(context)) {
98             return AppCompatDelegate.MODE_NIGHT_NO;
99         }
100         if (isSystemNightMode(context)) {
101             return AppCompatDelegate.MODE_NIGHT_YES;
102         }
103         return AppCompatDelegate.MODE_NIGHT_NO;
104     }
105 
106     /**
107      * Forces the web pages shown by the {@link android.webkit.WebView} which has the
108      * supplied {@code webSettings} to have the appropriate day/night mode depending
109      * on the app theme.
110      */
applyWebSettingsDayNight(Context context, WebSettings webSettings, Intent intent)111     public void applyWebSettingsDayNight(Context context, WebSettings webSettings, Intent intent) {
112         requireNonNull(context);
113         requireNonNull(webSettings);
114         if (!WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
115             return;
116         }
117         WebSettingsCompat.setForceDark(webSettings, getForceDarkMode(context, intent));
118     }
119 
120     /**
121      * Updates the relevant animation with theme-specific colors.
122      * <p>If the supplied {@link LottieAnimationView} does not have a loaded {@link
123      * LottieComposition}, it asynchronously waits for it to load and then applies the colors.
124      */
setupAnimationDynamicColors( Context context, LottieAnimationView lottieAnimationView, Intent intent)125     public void setupAnimationDynamicColors(
126             Context context, LottieAnimationView lottieAnimationView, Intent intent) {
127         mAnimationDynamicColorsHelper.setupAnimationDynamicColors(
128                 new LottieAnimationWrapper(lottieAnimationView),
129                 getDefaultNightMode(context, intent));
130     }
131 
getForceDarkMode(Context context, Intent intent)132     private int getForceDarkMode(Context context, Intent intent) {
133         if (getDefaultNightMode(context, intent) == AppCompatDelegate.MODE_NIGHT_YES) {
134             return WebSettingsCompat.FORCE_DARK_ON;
135         } else {
136             return WebSettingsCompat.FORCE_DARK_OFF;
137         }
138     }
139 
shouldSuppressDayNight(Context context)140     private boolean shouldSuppressDayNight(Context context) {
141         if (!FLAG_ENABLE_LIGHT_DARK_MODE) {
142             return true;
143         }
144         return !mSetupWizardBridge.isSetupWizardDayNightEnabled(context);
145     }
146 
isSystemNightMode(Context context)147     private boolean isSystemNightMode(Context context) {
148         return mNightModeChecker.isSystemNightMode(context);
149     }
150 
getDefaultThemeName(Context context, Intent intent)151     private String getDefaultThemeName(Context context, Intent intent) {
152         String theme = getProvidedTheme(intent);
153         if (TextUtils.isEmpty(theme)) {
154             if (isSystemNightMode(context)) {
155                 theme = com.google.android.setupdesign.util.ThemeHelper.THEME_GLIF_V4;
156             } else {
157                 theme = com.google.android.setupdesign.util.ThemeHelper.THEME_GLIF_V4_LIGHT;
158             }
159         }
160         return theme;
161     }
162 
getProvidedTheme(Intent intent)163     private String getProvidedTheme(Intent intent) {
164         String theme = intent.getStringExtra(WizardManagerHelper.EXTRA_THEME);
165         if (TextUtils.isEmpty(theme)) {
166             return mSetupWizardBridge.getSystemPropertySetupWizardTheme();
167         }
168         return theme;
169     }
170 
171     interface SetupWizardBridge {
isSetupWizardDayNightEnabled(Context context)172         boolean isSetupWizardDayNightEnabled(Context context);
173 
getSystemPropertySetupWizardTheme()174         String getSystemPropertySetupWizardTheme();
175 
resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight)176         int resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight);
177     }
178 
179     interface NightModeChecker {
isSystemNightMode(Context context)180         boolean isSystemNightMode(Context context);
181     }
182 
183     /**
184      * Default implementation of {@link NightModeChecker}.
185      */
186     public static class DefaultNightModeChecker implements NightModeChecker {
187         @Override
isSystemNightMode(Context context)188         public boolean isSystemNightMode(Context context) {
189             return (context.getResources().getConfiguration().uiMode & UI_MODE_NIGHT_MASK)
190                     == UI_MODE_NIGHT_YES;
191         }
192     }
193 
194     /**
195      * Default implementation of {@link SetupWizardBridge}.
196      */
197     public static class DefaultSetupWizardBridge implements SetupWizardBridge {
198         @Override
isSetupWizardDayNightEnabled(Context context)199         public boolean isSetupWizardDayNightEnabled(Context context) {
200             return com.google.android.setupdesign.util.ThemeHelper
201                     .isSetupWizardDayNightEnabled(context);
202         }
203 
204         @Override
getSystemPropertySetupWizardTheme()205         public String getSystemPropertySetupWizardTheme() {
206             return SYSTEM_PROPERTY_SETUPWIZARD_THEME;
207         }
208 
209         @Override
resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight)210         public int resolveTheme(int defaultTheme, String themeName, boolean suppressDayNight) {
211             ThemeResolver themeResolver = new ThemeResolver.Builder(ThemeResolver.getDefault())
212                     .setDefaultTheme(defaultTheme)
213                     .setUseDayNight(true)
214                     .build();
215             return themeResolver.resolve(
216                     themeName,
217                     suppressDayNight);
218         }
219     }
220 }
221