• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.customization.model.color;
17 
18 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
19 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE;
20 import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;
21 import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_BOTH;
22 import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_INDEX;
23 import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_SOURCE;
24 import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_THEME_STYLE;
25 
26 import android.app.WallpaperColors;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.database.ContentObserver;
30 import android.net.Uri;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.provider.Settings;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.customization.model.CustomizationManager;
41 import com.android.customization.model.ResourceConstants;
42 import com.android.customization.model.color.ColorOptionsProvider.ColorSource;
43 import com.android.customization.model.theme.OverlayManagerCompat;
44 import com.android.wallpaper.R;
45 
46 import org.json.JSONArray;
47 import org.json.JSONException;
48 import org.json.JSONObject;
49 
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Iterator;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.concurrent.ExecutorService;
56 import java.util.concurrent.Executors;
57 
58 /** The Color manager to manage Color bundle related operations. */
59 public class ColorCustomizationManager implements CustomizationManager<ColorOption> {
60 
61     private static final String TAG = "ColorCustomizationManager";
62     private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor();
63 
64     private static final Set<String> COLOR_OVERLAY_SETTINGS = new HashSet<>();
65     static {
66         COLOR_OVERLAY_SETTINGS.add(OVERLAY_CATEGORY_SYSTEM_PALETTE);
67         COLOR_OVERLAY_SETTINGS.add(OVERLAY_CATEGORY_COLOR);
68         COLOR_OVERLAY_SETTINGS.add(OVERLAY_COLOR_SOURCE);
69         COLOR_OVERLAY_SETTINGS.add(OVERLAY_THEME_STYLE);
70     }
71 
72     private static ColorCustomizationManager sColorCustomizationManager;
73 
74     private final ColorOptionsProvider mProvider;
75     private final OverlayManagerCompat mOverlayManagerCompat;
76     private final ContentResolver mContentResolver;
77     private final ContentObserver mObserver;
78 
79     private Map<String, String> mCurrentOverlays;
80     @ColorSource private String mCurrentSource;
81     private String mCurrentStyle;
82     private WallpaperColors mHomeWallpaperColors;
83     private WallpaperColors mLockWallpaperColors;
84 
85     /** Returns the {@link ColorCustomizationManager} instance. */
getInstance(Context context, OverlayManagerCompat overlayManagerCompat)86     public static ColorCustomizationManager getInstance(Context context,
87             OverlayManagerCompat overlayManagerCompat) {
88         if (sColorCustomizationManager == null) {
89             Context appContext = context.getApplicationContext();
90             sColorCustomizationManager = new ColorCustomizationManager(
91                     new ColorProvider(appContext,
92                             appContext.getString(R.string.themes_stub_package)),
93                     appContext.getContentResolver(), overlayManagerCompat);
94         }
95         return sColorCustomizationManager;
96     }
97 
98     @VisibleForTesting
ColorCustomizationManager(ColorOptionsProvider provider, ContentResolver contentResolver, OverlayManagerCompat overlayManagerCompat)99     ColorCustomizationManager(ColorOptionsProvider provider, ContentResolver contentResolver,
100             OverlayManagerCompat overlayManagerCompat) {
101         mProvider = provider;
102         mContentResolver = contentResolver;
103         mObserver = new ContentObserver(/* handler= */ null) {
104             @Override
105             public void onChange(boolean selfChange, Uri uri) {
106                 super.onChange(selfChange, uri);
107                 // Resets current overlays when system's theme setting is changed.
108                 if (TextUtils.equals(uri.getLastPathSegment(), ResourceConstants.THEME_SETTING)) {
109                     Log.i(TAG, "Resetting " + mCurrentOverlays + ", " + mCurrentStyle + ", "
110                             + mCurrentSource + " to null");
111                     mCurrentOverlays = null;
112                     mCurrentStyle = null;
113                     mCurrentSource = null;
114                 }
115             }
116         };
117         mContentResolver.registerContentObserver(
118                 Settings.Secure.CONTENT_URI, /* notifyForDescendants= */ true, mObserver);
119         mOverlayManagerCompat = overlayManagerCompat;
120     }
121 
122     @Override
isAvailable()123     public boolean isAvailable() {
124         return mOverlayManagerCompat.isAvailable() && mProvider.isAvailable();
125     }
126 
127     @Override
apply(ColorOption theme, Callback callback)128     public void apply(ColorOption theme, Callback callback) {
129         applyOverlays(theme, callback);
130     }
131 
applyOverlays(ColorOption colorOption, Callback callback)132     private void applyOverlays(ColorOption colorOption, Callback callback) {
133         sExecutorService.submit(() -> {
134             String currentStoredOverlays = getStoredOverlays();
135             if (TextUtils.isEmpty(currentStoredOverlays)) {
136                 currentStoredOverlays = "{}";
137             }
138             JSONObject overlaysJson = null;
139             try {
140                 overlaysJson = new JSONObject(currentStoredOverlays);
141                 JSONObject colorJson = colorOption.getJsonPackages(true);
142                 for (String setting : COLOR_OVERLAY_SETTINGS) {
143                     overlaysJson.remove(setting);
144                 }
145                 for (Iterator<String> it = colorJson.keys(); it.hasNext(); ) {
146                     String key = it.next();
147                     overlaysJson.put(key, colorJson.get(key));
148                 }
149                 overlaysJson.put(OVERLAY_COLOR_SOURCE, colorOption.getSource());
150                 overlaysJson.put(OVERLAY_COLOR_INDEX, String.valueOf(colorOption.getIndex()));
151                 overlaysJson.put(OVERLAY_THEME_STYLE,
152                         String.valueOf(colorOption.getStyle().toString()));
153 
154                 // OVERLAY_COLOR_BOTH is only for wallpaper color case, not preset.
155                 if (!COLOR_SOURCE_PRESET.equals(colorOption.getSource())) {
156                     boolean isForBoth =
157                             (mLockWallpaperColors == null || mLockWallpaperColors.equals(
158                                     mHomeWallpaperColors));
159                     overlaysJson.put(OVERLAY_COLOR_BOTH, isForBoth ? "1" : "0");
160                 } else {
161                     overlaysJson.remove(OVERLAY_COLOR_BOTH);
162                 }
163             } catch (JSONException e) {
164                 e.printStackTrace();
165             }
166             boolean allApplied = overlaysJson != null && Settings.Secure.putString(
167                     mContentResolver, ResourceConstants.THEME_SETTING, overlaysJson.toString());
168             new Handler(Looper.getMainLooper()).post(() -> {
169                 if (allApplied) {
170                     callback.onSuccess();
171                 } else {
172                     callback.onError(null);
173                 }
174             });
175         });
176     }
177 
178     @Override
fetchOptions(OptionsFetchedListener<ColorOption> callback, boolean reload)179     public void fetchOptions(OptionsFetchedListener<ColorOption> callback, boolean reload) {
180         WallpaperColors lockWallpaperColors = mLockWallpaperColors;
181         if (lockWallpaperColors != null && mLockWallpaperColors.equals(mHomeWallpaperColors)) {
182             lockWallpaperColors = null;
183         }
184         mProvider.fetch(callback, reload, mHomeWallpaperColors,
185                 lockWallpaperColors,  /* shouldUseRevampedUi= */ false);
186     }
187 
188     /**
189      * Fetch options function for the customization hub revamped UI
190      *
191      * TODO (b/276417460): refactor to reduce code repetition with the other fetch options function
192      */
fetchRevampedUIOptions(OptionsFetchedListener<ColorOption> callback, boolean reload)193     public void fetchRevampedUIOptions(OptionsFetchedListener<ColorOption> callback,
194             boolean reload) {
195         WallpaperColors lockWallpaperColors = mLockWallpaperColors;
196         if (lockWallpaperColors != null && mLockWallpaperColors.equals(mHomeWallpaperColors)) {
197             lockWallpaperColors = null;
198         }
199         mProvider.fetch(callback, reload, mHomeWallpaperColors,
200                 lockWallpaperColors,  /* shouldUseRevampedUi= */ true);
201     }
202 
203     /**
204      * Sets the current wallpaper colors to extract seeds from
205      */
setWallpaperColors(WallpaperColors homeColors, @Nullable WallpaperColors lockColors)206     public void setWallpaperColors(WallpaperColors homeColors,
207             @Nullable WallpaperColors lockColors) {
208         mHomeWallpaperColors = homeColors;
209         mLockWallpaperColors = lockColors;
210     }
211 
212     /**
213      * Gets current overlays mapping
214      * @return the {@link Map} of overlays
215      */
getCurrentOverlays()216     public Map<String, String> getCurrentOverlays() {
217         if (mCurrentOverlays == null) {
218             parseSettings(getStoredOverlays());
219         }
220         return mCurrentOverlays;
221     }
222 
223     /**
224      * @return The source of the currently applied color. One of
225      * {@link ColorOptionsProvider#COLOR_SOURCE_HOME},{@link ColorOptionsProvider#COLOR_SOURCE_LOCK}
226      * or {@link ColorOptionsProvider#COLOR_SOURCE_PRESET}.
227      */
228     @ColorSource
getCurrentColorSource()229     public @Nullable String getCurrentColorSource() {
230         if (mCurrentSource == null) {
231             parseSettings(getStoredOverlays());
232         }
233         return mCurrentSource;
234     }
235 
236     /**
237      * @return The style of the currently applied color. One of enum values in
238      * {@link com.android.systemui.monet.Style}.
239      */
getCurrentStyle()240     public @Nullable String getCurrentStyle() {
241         if (mCurrentStyle == null) {
242             parseSettings(getStoredOverlays());
243         }
244         return mCurrentStyle;
245     }
246 
getStoredOverlays()247     public String getStoredOverlays() {
248         return Settings.Secure.getString(mContentResolver, ResourceConstants.THEME_SETTING);
249     }
250 
251     @VisibleForTesting
parseSettings(String serializedJson)252     void parseSettings(String serializedJson) {
253         Map<String, String> allSettings = parseColorSettings(serializedJson);
254         mCurrentSource = allSettings.remove(OVERLAY_COLOR_SOURCE);
255         mCurrentStyle = allSettings.remove(OVERLAY_THEME_STYLE);
256         mCurrentOverlays = allSettings;
257     }
258 
parseColorSettings(String serializedJsonSettings)259     private Map<String, String> parseColorSettings(String serializedJsonSettings) {
260         Map<String, String> overlayPackages = new HashMap<>();
261         if (serializedJsonSettings != null) {
262             try {
263                 final JSONObject jsonPackages = new JSONObject(serializedJsonSettings);
264 
265                 JSONArray names = jsonPackages.names();
266                 if (names != null) {
267                     for (int i = 0; i < names.length(); i++) {
268                         String category = names.getString(i);
269                         if (COLOR_OVERLAY_SETTINGS.contains(category)) {
270                             try {
271                                 overlayPackages.put(category, jsonPackages.getString(category));
272                             } catch (JSONException e) {
273                                 Log.e(TAG, "parseColorOverlays: " + e.getLocalizedMessage(), e);
274                             }
275                         }
276                     }
277                 }
278             } catch (JSONException e) {
279                 Log.e(TAG, "parseColorOverlays: " + e.getLocalizedMessage(), e);
280             }
281         }
282         return overlayPackages;
283     }
284 }
285