blob: 791a9a0a081474c276c2f1b1278ec724ffb0c011 [file] [log] [blame]
Catherine Liang82703622022-12-21 18:04:54 +00001/*
2 * Copyright (C) 2023 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 */
16package com.android.customization.model.color;
17
18import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME;
19import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK;
20import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;
21
22import android.app.Activity;
23import android.app.WallpaperColors;
24import android.content.Context;
25import android.os.Handler;
26import android.os.SystemClock;
27import android.stats.style.StyleEnums;
28import android.text.TextUtils;
29import android.util.Log;
30import android.view.LayoutInflater;
31
32import androidx.annotation.Nullable;
33import androidx.lifecycle.LifecycleOwner;
34
35import com.android.customization.model.CustomizationManager;
36import com.android.customization.model.theme.OverlayManagerCompat;
37import com.android.customization.module.CustomizationInjector;
38import com.android.customization.module.ThemesUserEventLogger;
Catherine Liang6f794ed2023-01-05 16:12:24 +000039import com.android.customization.picker.color.ui.fragment.ColorPickerFragment;
40import com.android.customization.picker.color.ui.view.ColorSectionView2;
Catherine Liang82703622022-12-21 18:04:54 +000041import com.android.wallpaper.R;
42import com.android.wallpaper.model.CustomizationSectionController;
43import com.android.wallpaper.model.WallpaperColorsViewModel;
44import com.android.wallpaper.module.InjectorProvider;
45
46import com.google.common.collect.Iterables;
47import com.google.common.collect.Lists;
48
49import java.util.ArrayList;
50import java.util.List;
51
52/**
53 * Color section view's controller for the logic of color customization.
Catherine Liang6f794ed2023-01-05 16:12:24 +000054 *
55 * TODO (b/262924584): Convert ColorSectionController2 into Kotlin & use new architecture
Catherine Liang82703622022-12-21 18:04:54 +000056 */
57public class ColorSectionController2 implements CustomizationSectionController<ColorSectionView2> {
58
59 private static final String TAG = "ColorSectionController";
60 private static final long MIN_COLOR_APPLY_PERIOD = 500L;
61
62 private final ThemesUserEventLogger mEventLogger;
63 private final ColorCustomizationManager mColorManager;
64 private final WallpaperColorsViewModel mWallpaperColorsViewModel;
65 private final LifecycleOwner mLifecycleOwner;
66 private final CustomizationSectionNavigationController mSectionNavigationController;
67
68 private List<ColorOption> mWallpaperColorOptions = new ArrayList<>();
69 private List<ColorOption> mPresetColorOptions = new ArrayList<>();
70 private ColorOption mSelectedColor;
71 @Nullable private WallpaperColors mHomeWallpaperColors;
72 @Nullable private WallpaperColors mLockWallpaperColors;
73 // Uses a boolean value to indicate whether wallpaper color is ready because WallpaperColors
74 // maybe be null when it's ready.
75 private boolean mHomeWallpaperColorsReady;
76 private boolean mLockWallpaperColorsReady;
77 private long mLastColorApplyingTime = 0L;
78 private ColorSectionView2 mColorSectionView;
79
80 public ColorSectionController2(Activity activity, WallpaperColorsViewModel viewModel,
81 LifecycleOwner lifecycleOwner,
82 CustomizationSectionNavigationController sectionNavigationController) {
83 CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
84 mEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(activity);
85 mColorManager = ColorCustomizationManager.getInstance(activity,
86 new OverlayManagerCompat(activity));
87 mWallpaperColorsViewModel = viewModel;
88 mLifecycleOwner = lifecycleOwner;
89 mSectionNavigationController = sectionNavigationController;
90 }
91
92 @Override
93 public boolean isAvailable(@Nullable Context context) {
94 return context != null && ColorUtils.isMonetEnabled(context) && mColorManager.isAvailable();
95 }
96
97 @Override
98 public ColorSectionView2 createView(Context context) {
99 mColorSectionView = (ColorSectionView2) LayoutInflater.from(context).inflate(
100 R.layout.color_section_view2, /* root= */ null);
101
Catherine Liang6f794ed2023-01-05 16:12:24 +0000102 mWallpaperColorsViewModel.getHomeWallpaperColorsLiveData().observe(mLifecycleOwner,
Catherine Liang82703622022-12-21 18:04:54 +0000103 homeColors -> {
104 mHomeWallpaperColors = homeColors;
105 mHomeWallpaperColorsReady = true;
106 maybeLoadColors();
107 });
Catherine Liang6f794ed2023-01-05 16:12:24 +0000108 mWallpaperColorsViewModel.getLockWallpaperColorsLiveData().observe(mLifecycleOwner,
Catherine Liang82703622022-12-21 18:04:54 +0000109 lockColors -> {
110 mLockWallpaperColors = lockColors;
111 mLockWallpaperColorsReady = true;
112 maybeLoadColors();
113 });
114 return mColorSectionView;
115 }
116
117 private void maybeLoadColors() {
118 if (mHomeWallpaperColorsReady && mLockWallpaperColorsReady) {
119 mColorManager.setWallpaperColors(mHomeWallpaperColors, mLockWallpaperColors);
120 loadColorOptions(/* reload= */ false);
121 }
122 }
123
124 private void loadColorOptions(boolean reload) {
125 mColorManager.fetchOptions(new CustomizationManager.OptionsFetchedListener<ColorOption>() {
126 @Override
127 public void onOptionsLoaded(List<ColorOption> options) {
128 List<ColorOption> wallpaperColorOptions = new ArrayList<>();
129 List<ColorOption> presetColorOptions = new ArrayList<>();
130 for (ColorOption option : options) {
131 if (option instanceof ColorSeedOption) {
132 wallpaperColorOptions.add(option);
133 } else if (option instanceof ColorBundle) {
134 presetColorOptions.add(option);
135 }
136 }
137 mWallpaperColorOptions = wallpaperColorOptions;
138 mPresetColorOptions = presetColorOptions;
139 mSelectedColor = findActiveColorOption(mWallpaperColorOptions,
140 mPresetColorOptions);
141
142 mColorSectionView.post(() -> setUpColorSectionView(mWallpaperColorOptions,
143 mPresetColorOptions));
144 }
145
146 @Override
147 public void onError(@Nullable Throwable throwable) {
148 if (throwable != null) {
149 Log.e(TAG, "Error loading theme bundles", throwable);
150 }
151 }
152 }, reload);
153 }
154
155 private void setUpColorSectionView(List<ColorOption> wallpaperColorOptions,
156 List<ColorOption> presetColorOptions) {
157 int wallpaperOptionSize = wallpaperColorOptions.size();
158
159 List<ColorOption> subOptions = wallpaperColorOptions.subList(0,
160 Math.min(5, wallpaperOptionSize));
161 // add additional options based on preset colors if there are less than 5 wallpaper colors
162 List<ColorOption> additionalSubOptions = presetColorOptions.subList(0,
163 Math.min(Math.max(0, 5 - wallpaperOptionSize), presetColorOptions.size()));
164 subOptions.addAll(additionalSubOptions);
165
166 mColorSectionView.setOverflowOnClick(() -> {
167 mSectionNavigationController.navigateTo(new ColorPickerFragment());
168 return null;
169 });
170 mColorSectionView.setColorOptionOnClick(selectedOption -> {
171 if (mSelectedColor.equals(selectedOption)) {
172 return null;
173 }
174 mSelectedColor = (ColorOption) selectedOption;
175 // Post with delay for color option to run ripple.
176 new Handler().postDelayed(()-> applyColor(mSelectedColor), /* delayMillis= */ 100);
177 return null;
178 });
179 mColorSectionView.setItems(subOptions, mColorManager);
180 }
181
182 private ColorOption findActiveColorOption(List<ColorOption> wallpaperColorOptions,
183 List<ColorOption> presetColorOptions) {
184 ColorOption activeColorOption = null;
185 for (ColorOption colorOption : Lists.newArrayList(
186 Iterables.concat(wallpaperColorOptions, presetColorOptions))) {
187 if (colorOption.isActive(mColorManager)) {
188 activeColorOption = colorOption;
189 break;
190 }
191 }
192 // Use the first one option by default. This should not happen as above should have an
193 // active option found.
194 if (activeColorOption == null) {
195 activeColorOption = wallpaperColorOptions.isEmpty()
196 ? presetColorOptions.get(0)
197 : wallpaperColorOptions.get(0);
198 }
199 return activeColorOption;
200 }
201
202 private void applyColor(ColorOption colorOption) {
203 if (SystemClock.elapsedRealtime() - mLastColorApplyingTime < MIN_COLOR_APPLY_PERIOD) {
204 return;
205 }
206 mLastColorApplyingTime = SystemClock.elapsedRealtime();
207 mColorManager.apply(colorOption, new CustomizationManager.Callback() {
208 @Override
209 public void onSuccess() {
210 mColorSectionView.announceForAccessibility(
211 mColorSectionView.getContext().getString(R.string.color_changed));
212 mEventLogger.logColorApplied(getColorAction(colorOption), colorOption);
213 }
214
215 @Override
216 public void onError(@Nullable Throwable throwable) {
217 Log.w(TAG, "Apply theme with error: " + throwable);
218 }
219 });
220 }
221
222 private int getColorAction(ColorOption colorOption) {
223 int action = StyleEnums.DEFAULT_ACTION;
224 boolean isForBoth = mLockWallpaperColors == null || mLockWallpaperColors.equals(
225 mHomeWallpaperColors);
226
227 if (TextUtils.equals(colorOption.getSource(), COLOR_SOURCE_PRESET)) {
228 action = StyleEnums.COLOR_PRESET_APPLIED;
229 } else if (isForBoth) {
230 action = StyleEnums.COLOR_WALLPAPER_HOME_LOCK_APPLIED;
231 } else {
232 switch (colorOption.getSource()) {
233 case COLOR_SOURCE_HOME:
234 action = StyleEnums.COLOR_WALLPAPER_HOME_APPLIED;
235 break;
236 case COLOR_SOURCE_LOCK:
237 action = StyleEnums.COLOR_WALLPAPER_LOCK_APPLIED;
238 break;
239 }
240 }
241 return action;
242 }
243}