1 /*
2  * Copyright (C) 2019 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.systemui.theme;
17 
18 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
19 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
20 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
21 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
22 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
23 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
24 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_BOTH;
25 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX;
26 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
27 import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
28 
29 import android.annotation.Nullable;
30 import android.app.WallpaperColors;
31 import android.app.WallpaperManager;
32 import android.app.WallpaperManager.OnColorsChangedListener;
33 import android.content.BroadcastReceiver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.om.FabricatedOverlay;
38 import android.content.om.OverlayIdentifier;
39 import android.content.pm.UserInfo;
40 import android.content.res.Configuration;
41 import android.database.ContentObserver;
42 import android.graphics.Color;
43 import android.net.Uri;
44 import android.os.Handler;
45 import android.os.UserHandle;
46 import android.os.UserManager;
47 import android.provider.Settings;
48 import android.text.TextUtils;
49 import android.util.ArrayMap;
50 import android.util.Log;
51 import android.util.SparseArray;
52 import android.util.SparseIntArray;
53 import android.util.TypedValue;
54 
55 import androidx.annotation.NonNull;
56 
57 import com.android.internal.graphics.ColorUtils;
58 import com.android.systemui.Dumpable;
59 import com.android.systemui.SystemUI;
60 import com.android.systemui.broadcast.BroadcastDispatcher;
61 import com.android.systemui.dagger.SysUISingleton;
62 import com.android.systemui.dagger.qualifiers.Background;
63 import com.android.systemui.dagger.qualifiers.Main;
64 import com.android.systemui.dump.DumpManager;
65 import com.android.systemui.flags.FeatureFlags;
66 import com.android.systemui.keyguard.WakefulnessLifecycle;
67 import com.android.systemui.monet.ColorScheme;
68 import com.android.systemui.settings.UserTracker;
69 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
70 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
71 import com.android.systemui.util.settings.SecureSettings;
72 
73 import org.json.JSONException;
74 import org.json.JSONObject;
75 
76 import java.io.FileDescriptor;
77 import java.io.PrintWriter;
78 import java.util.Collection;
79 import java.util.HashSet;
80 import java.util.List;
81 import java.util.Map;
82 import java.util.Set;
83 import java.util.concurrent.Executor;
84 import java.util.stream.Collectors;
85 
86 import javax.inject.Inject;
87 
88 /**
89  * Controls the application of theme overlays across the system for all users.
90  * This service is responsible for:
91  * - Observing changes to Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES and applying the
92  * corresponding overlays across the system
93  * - Observing user switches, applying the overlays for the current user to user 0 (for systemui)
94  * - Observing work profile changes and applying overlays from the primary user to their
95  * associated work profiles
96  */
97 @SysUISingleton
98 public class ThemeOverlayController extends SystemUI implements Dumpable {
99     protected static final String TAG = "ThemeOverlayController";
100     private static final boolean DEBUG = true;
101 
102     protected static final int NEUTRAL = 0;
103     protected static final int ACCENT = 1;
104 
105     private final ThemeOverlayApplier mThemeManager;
106     private final UserManager mUserManager;
107     private final BroadcastDispatcher mBroadcastDispatcher;
108     private final Executor mBgExecutor;
109     private final SecureSettings mSecureSettings;
110     private final Executor mMainExecutor;
111     private final Handler mBgHandler;
112     private final boolean mIsMonetEnabled;
113     private final UserTracker mUserTracker;
114     private final DeviceProvisionedController mDeviceProvisionedController;
115     // Current wallpaper colors associated to a user.
116     private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
117     private final WallpaperManager mWallpaperManager;
118     private ColorScheme mColorScheme;
119     // If fabricated overlays were already created for the current theme.
120     private boolean mNeedsOverlayCreation;
121     // Dominant color extracted from wallpaper, NOT the color used on the overlay
122     protected int mMainWallpaperColor = Color.TRANSPARENT;
123     // Accent color extracted from wallpaper, NOT the color used on the overlay
124     protected int mWallpaperAccentColor = Color.TRANSPARENT;
125     // Accent colors overlay
126     private FabricatedOverlay mSecondaryOverlay;
127     // Neutral system colors overlay
128     private FabricatedOverlay mNeutralOverlay;
129     // If wallpaper color event will be accepted and change the UI colors.
130     private boolean mAcceptColorEvents = true;
131     // If non-null (per user), colors that were sent to the framework, and processing was deferred
132     // until the next time the screen is off.
133     private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
134     private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
135     private final WakefulnessLifecycle mWakefulnessLifecycle;
136 
137     // Defers changing themes until Setup Wizard is done.
138     private boolean mDeferredThemeEvaluation;
139     // Determines if we should ignore THEME_CUSTOMIZATION_OVERLAY_PACKAGES setting changes.
140     private boolean mSkipSettingChange;
141 
142     private final DeviceProvisionedListener mDeviceProvisionedListener =
143             new DeviceProvisionedListener() {
144                 @Override
145                 public void onUserSetupChanged() {
146                     if (!mDeviceProvisionedController.isCurrentUserSetup()) {
147                         return;
148                     }
149                     if (!mDeferredThemeEvaluation) {
150                         return;
151                     }
152                     Log.i(TAG, "Applying deferred theme");
153                     mDeferredThemeEvaluation = false;
154                     reevaluateSystemTheme(true /* forceReload */);
155                 }
156             };
157 
158     private final OnColorsChangedListener mOnColorsChangedListener = new OnColorsChangedListener() {
159         @Override
160         public void onColorsChanged(WallpaperColors wallpaperColors, int which) {
161             throw new IllegalStateException("This should never be invoked, all messages should "
162                     + "arrive on the overload that has a user id");
163         }
164 
165         @Override
166         public void onColorsChanged(WallpaperColors wallpaperColors, int which, int userId) {
167             boolean currentUser = userId == mUserTracker.getUserId();
168             if (currentUser && !mAcceptColorEvents
169                     && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
170                 mDeferredWallpaperColors.put(userId, wallpaperColors);
171                 mDeferredWallpaperColorsFlags.put(userId, which);
172                 Log.i(TAG, "colors received; processing deferred until screen off: "
173                         + wallpaperColors + " user: " + userId);
174                 return;
175             }
176 
177             if (currentUser && wallpaperColors != null) {
178                 mAcceptColorEvents = false;
179                 // Any cache of colors deferred for process is now stale.
180                 mDeferredWallpaperColors.put(userId, null);
181                 mDeferredWallpaperColorsFlags.put(userId, 0);
182             }
183 
184             handleWallpaperColors(wallpaperColors, which, userId);
185         }
186     };
187 
188     private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
189         @Override
190         public void onUserChanged(int newUser, @NonNull Context userContext) {
191             boolean isManagedProfile = mUserManager.isManagedProfile(newUser);
192             if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
193                 Log.i(TAG, "User setup not finished when new user event was received. "
194                         + "Deferring... Managed profile? " + isManagedProfile);
195                 return;
196             }
197             if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
198             reevaluateSystemTheme(true /* forceReload */);
199         }
200     };
201 
getLatestWallpaperType(int userId)202     private int getLatestWallpaperType(int userId) {
203         return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
204                 > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
205                 ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
206     }
207 
isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors)208     private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
209         if (newWallpaperColors == null) {
210             return false;
211         }
212         // Gets the color that was overridden in the theme setting if any.
213         String sysPaletteColor = (String) jsonObject.opt(OVERLAY_CATEGORY_SYSTEM_PALETTE);
214         if (sysPaletteColor == null) {
215             return false;
216         }
217         if (!sysPaletteColor.startsWith("#")) {
218             sysPaletteColor = "#" + sysPaletteColor;
219         }
220         final int systemPaletteColorArgb = Color.parseColor(sysPaletteColor);
221         // Gets seed colors from incoming {@link WallpaperColors} instance.
222         List<Integer> seedColors = ColorScheme.getSeedColors(newWallpaperColors);
223         for (int seedColor : seedColors) {
224             // The seed color from incoming {@link WallpaperColors} instance
225             // was set as color override.
226             if (seedColor == systemPaletteColorArgb) {
227                 if (DEBUG) {
228                     Log.d(TAG, "Same as previous set system palette: " + sysPaletteColor);
229                 }
230                 return true;
231             }
232         }
233         return false;
234     }
235 
handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId)236     private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
237         final int currentUser = mUserTracker.getUserId();
238         final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
239         int latestWallpaperType = getLatestWallpaperType(userId);
240         if ((flags & latestWallpaperType) != 0) {
241             mCurrentColors.put(userId, wallpaperColors);
242             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
243         }
244 
245         if (userId != currentUser) {
246             Log.d(TAG, "Colors " + wallpaperColors + " for user " + userId + ". "
247                     + "Not for current user: " + currentUser);
248             return;
249         }
250 
251         if (mDeviceProvisionedController != null
252                 && !mDeviceProvisionedController.isCurrentUserSetup()) {
253             if (hadWallpaperColors) {
254                 Log.i(TAG, "Wallpaper color event deferred until setup is finished: "
255                         + wallpaperColors);
256                 mDeferredThemeEvaluation = true;
257                 return;
258             } else if (mDeferredThemeEvaluation) {
259                 Log.i(TAG, "Wallpaper color event received, but we already were deferring eval: "
260                         + wallpaperColors);
261                 return;
262             } else {
263                 if (DEBUG) {
264                     Log.i(TAG, "During user setup, but allowing first color event: had? "
265                             + hadWallpaperColors + " has? " + (mCurrentColors.get(userId) != null));
266                 }
267             }
268         }
269         // Check if we need to reset to default colors (if a color override was set that is sourced
270         // from the wallpaper)
271         String overlayPackageJson = mSecureSettings.getStringForUser(
272                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
273                 currentUser);
274         boolean isDestinationBoth = (flags == (WallpaperManager.FLAG_SYSTEM
275                 | WallpaperManager.FLAG_LOCK));
276         try {
277             JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
278                     : new JSONObject(overlayPackageJson);
279             // The latest applied wallpaper should be the source of system colors when:
280             // There is not preset color applied and the incoming wallpaper color is not applied
281             if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
282                     && ((flags & latestWallpaperType) != 0 && !isSeedColorSet(jsonObject,
283                     wallpaperColors))) {
284                 mSkipSettingChange = true;
285                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
286                         OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
287                     jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
288                     jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
289                     jsonObject.remove(OVERLAY_COLOR_INDEX);
290                 }
291                 // Keep color_both value because users can change either or both home and
292                 // lock screen wallpapers.
293                 jsonObject.put(OVERLAY_COLOR_BOTH, isDestinationBoth ? "1" : "0");
294 
295                 jsonObject.put(OVERLAY_COLOR_SOURCE,
296                         (flags == WallpaperManager.FLAG_LOCK) ? COLOR_SOURCE_LOCK
297                                 : COLOR_SOURCE_HOME);
298                 jsonObject.put(TIMESTAMP_FIELD, System.currentTimeMillis());
299                 if (DEBUG) {
300                     Log.d(TAG, "Updating theme setting from "
301                             + overlayPackageJson + " to " + jsonObject.toString());
302                 }
303                 mSecureSettings.putString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
304                         jsonObject.toString());
305             }
306         } catch (JSONException e) {
307             Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
308         }
309         reevaluateSystemTheme(false /* forceReload */);
310     }
311 
312     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
313         @Override
314         public void onReceive(Context context, Intent intent) {
315             boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction());
316             boolean isManagedProfile = mUserManager.isManagedProfile(
317                     intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
318             if (newWorkProfile) {
319                 if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
320                     Log.i(TAG, "User setup not finished when " + intent.getAction()
321                             + " was received. Deferring... Managed profile? " + isManagedProfile);
322                     return;
323                 }
324                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
325                 reevaluateSystemTheme(true /* forceReload */);
326             } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
327                 if (intent.getBooleanExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, false)) {
328                     mAcceptColorEvents = true;
329                     Log.i(TAG, "Wallpaper changed, allowing color events again");
330                 } else {
331                     Log.i(TAG, "Wallpaper changed from background app, "
332                             + "keep deferring color events. Accepting: " + mAcceptColorEvents);
333                 }
334             }
335         }
336     };
337 
338     @Inject
ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @Background Handler bgHandler, @Main Executor mainExecutor, @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier, SecureSettings secureSettings, WallpaperManager wallpaperManager, UserManager userManager, DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, WakefulnessLifecycle wakefulnessLifecycle)339     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
340             @Background Handler bgHandler, @Main Executor mainExecutor,
341             @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
342             SecureSettings secureSettings, WallpaperManager wallpaperManager,
343             UserManager userManager, DeviceProvisionedController deviceProvisionedController,
344             UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
345             WakefulnessLifecycle wakefulnessLifecycle) {
346         super(context);
347 
348         mIsMonetEnabled = featureFlags.isMonetEnabled();
349         mDeviceProvisionedController = deviceProvisionedController;
350         mBroadcastDispatcher = broadcastDispatcher;
351         mUserManager = userManager;
352         mBgExecutor = bgExecutor;
353         mMainExecutor = mainExecutor;
354         mBgHandler = bgHandler;
355         mThemeManager = themeOverlayApplier;
356         mSecureSettings = secureSettings;
357         mWallpaperManager = wallpaperManager;
358         mUserTracker = userTracker;
359         mWakefulnessLifecycle = wakefulnessLifecycle;
360         dumpManager.registerDumpable(TAG, this);
361     }
362 
363     @Override
start()364     public void start() {
365         if (DEBUG) Log.d(TAG, "Start");
366         final IntentFilter filter = new IntentFilter();
367         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
368         filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
369         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
370                 UserHandle.ALL);
371         mSecureSettings.registerContentObserverForUser(
372                 Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
373                 false,
374                 new ContentObserver(mBgHandler) {
375                     @Override
376                     public void onChange(boolean selfChange, Collection<Uri> collection, int flags,
377                             int userId) {
378                         if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId);
379                         if (mUserTracker.getUserId() != userId) {
380                             return;
381                         }
382                         if (!mDeviceProvisionedController.isUserSetup(userId)) {
383                             Log.i(TAG, "Theme application deferred when setting changed.");
384                             mDeferredThemeEvaluation = true;
385                             return;
386                         }
387                         if (mSkipSettingChange) {
388                             if (DEBUG) Log.d(TAG, "Skipping setting change");
389                             mSkipSettingChange = false;
390                             return;
391                         }
392                         reevaluateSystemTheme(true /* forceReload */);
393                     }
394                 },
395                 UserHandle.USER_ALL);
396 
397         if (!mIsMonetEnabled) {
398             return;
399         }
400 
401         mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
402 
403         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
404 
405         // Upon boot, make sure we have the most up to date colors
406         Runnable updateColors = () -> {
407             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
408                     getLatestWallpaperType(mUserTracker.getUserId()));
409             Runnable applyColors = () -> {
410                 if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
411                 mCurrentColors.put(mUserTracker.getUserId(), systemColor);
412                 reevaluateSystemTheme(false /* forceReload */);
413             };
414             if (mDeviceProvisionedController.isCurrentUserSetup()) {
415                 mMainExecutor.execute(applyColors);
416             } else {
417                 applyColors.run();
418             }
419         };
420 
421         // Whenever we're going directly to setup wizard, we need to process colors synchronously,
422         // otherwise we'll see some jank when the activity is recreated.
423         if (!mDeviceProvisionedController.isCurrentUserSetup()) {
424             updateColors.run();
425         } else {
426             mBgExecutor.execute(updateColors);
427         }
428         mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null,
429                 UserHandle.USER_ALL);
430         mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
431             @Override
432             public void onFinishedGoingToSleep() {
433                 final int userId = mUserTracker.getUserId();
434                 final WallpaperColors colors = mDeferredWallpaperColors.get(userId);
435                 if (colors != null) {
436                     int flags = mDeferredWallpaperColorsFlags.get(userId);
437 
438                     mDeferredWallpaperColors.put(userId, null);
439                     mDeferredWallpaperColorsFlags.put(userId, 0);
440 
441                     handleWallpaperColors(colors, flags, userId);
442                 }
443             }
444         });
445     }
446 
reevaluateSystemTheme(boolean forceReload)447     private void reevaluateSystemTheme(boolean forceReload) {
448         final WallpaperColors currentColors = mCurrentColors.get(mUserTracker.getUserId());
449         final int mainColor;
450         final int accentCandidate;
451         if (currentColors == null) {
452             mainColor = Color.TRANSPARENT;
453             accentCandidate = Color.TRANSPARENT;
454         } else {
455             mainColor = getNeutralColor(currentColors);
456             accentCandidate = getAccentColor(currentColors);
457         }
458 
459         if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate
460                 && !forceReload) {
461             return;
462         }
463 
464         mMainWallpaperColor = mainColor;
465         mWallpaperAccentColor = accentCandidate;
466 
467         if (mIsMonetEnabled) {
468             mSecondaryOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
469             mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
470             mNeedsOverlayCreation = true;
471             if (DEBUG) {
472                 Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay
473                         + " neutral: " + mNeutralOverlay);
474             }
475         }
476 
477         updateThemeOverlays();
478     }
479 
480     /**
481      * Return the main theme color from a given {@link WallpaperColors} instance.
482      */
getNeutralColor(@onNull WallpaperColors wallpaperColors)483     protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
484         return ColorScheme.getSeedColor(wallpaperColors);
485     }
486 
getAccentColor(@onNull WallpaperColors wallpaperColors)487     protected int getAccentColor(@NonNull WallpaperColors wallpaperColors) {
488         return ColorScheme.getSeedColor(wallpaperColors);
489     }
490 
491     /**
492      * Given a color candidate, return an overlay definition.
493      */
getOverlay(int color, int type)494     protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
495         boolean nightMode = (mContext.getResources().getConfiguration().uiMode
496                 & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
497 
498         mColorScheme = new ColorScheme(color, nightMode);
499         List<Integer> colorShades = type == ACCENT
500                 ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
501         String name = type == ACCENT ? "accent" : "neutral";
502         int paletteSize = mColorScheme.getAccent1().size();
503         FabricatedOverlay.Builder overlay =
504                 new FabricatedOverlay.Builder("com.android.systemui", name, "android");
505         for (int i = 0; i < colorShades.size(); i++) {
506             int luminosity = i % paletteSize;
507             int paletteIndex = i / paletteSize + 1;
508             String resourceName;
509             switch (luminosity) {
510                 case 0:
511                     resourceName = "android:color/system_" + name + paletteIndex + "_10";
512                     break;
513                 case 1:
514                     resourceName = "android:color/system_" + name + paletteIndex + "_50";
515                     break;
516                 default:
517                     int l = luminosity - 1;
518                     resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
519             }
520             overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
521                     ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
522         }
523 
524         return overlay.build();
525     }
526 
updateThemeOverlays()527     private void updateThemeOverlays() {
528         final int currentUser = mUserTracker.getUserId();
529         final String overlayPackageJson = mSecureSettings.getStringForUser(
530                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
531                 currentUser);
532         if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
533         final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
534         if (!TextUtils.isEmpty(overlayPackageJson)) {
535             try {
536                 JSONObject object = new JSONObject(overlayPackageJson);
537                 for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
538                     if (object.has(category)) {
539                         OverlayIdentifier identifier =
540                                 new OverlayIdentifier(object.getString(category));
541                         categoryToPackage.put(category, identifier);
542                     }
543                 }
544             } catch (JSONException e) {
545                 Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
546             }
547         }
548 
549         // Let's generate system overlay if the style picker decided to override it.
550         OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
551         if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
552             try {
553                 String colorString =  systemPalette.getPackageName().toLowerCase();
554                 if (!colorString.startsWith("#")) {
555                     colorString = "#" + colorString;
556                 }
557                 int color = Color.parseColor(colorString);
558                 mNeutralOverlay = getOverlay(color, NEUTRAL);
559                 mNeedsOverlayCreation = true;
560                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
561             } catch (Exception e) {
562                 // Color.parseColor doesn't catch any exceptions from the calls it makes
563                 Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName(), e);
564             }
565         } else if (!mIsMonetEnabled && systemPalette != null) {
566             try {
567                 // It's possible that we flipped the flag off and still have a @ColorInt in the
568                 // setting. We need to sanitize the input, otherwise the overlay transaction will
569                 // fail.
570                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
571             } catch (NumberFormatException e) {
572                 // This is a package name. All good, let's continue
573             }
574         }
575 
576         // Same for accent color.
577         OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
578         if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
579             try {
580                 String colorString =  accentPalette.getPackageName().toLowerCase();
581                 if (!colorString.startsWith("#")) {
582                     colorString = "#" + colorString;
583                 }
584                 int color = Color.parseColor(colorString);
585                 mSecondaryOverlay = getOverlay(color, ACCENT);
586                 mNeedsOverlayCreation = true;
587                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
588             } catch (Exception e) {
589                 // Color.parseColor doesn't catch any exceptions from the calls it makes
590                 Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName(), e);
591             }
592         } else if (!mIsMonetEnabled && accentPalette != null) {
593             try {
594                 Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
595                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
596             } catch (NumberFormatException e) {
597                 // This is a package name. All good, let's continue
598             }
599         }
600 
601         // Compatibility with legacy themes, where full packages were defined, instead of just
602         // colors.
603         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
604                 && mNeutralOverlay != null) {
605             categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
606                     mNeutralOverlay.getIdentifier());
607         }
608         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
609                 && mSecondaryOverlay != null) {
610             categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier());
611         }
612 
613         Set<UserHandle> managedProfiles = new HashSet<>();
614         for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) {
615             if (userInfo.isManagedProfile()) {
616                 managedProfiles.add(userInfo.getUserHandle());
617             }
618         }
619         if (DEBUG) {
620             Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
621                     .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
622                             Collectors.joining(", ")));
623         }
624         if (mNeedsOverlayCreation) {
625             mNeedsOverlayCreation = false;
626             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
627                     mSecondaryOverlay, mNeutralOverlay
628             }, currentUser, managedProfiles);
629         } else {
630             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
631                     managedProfiles);
632         }
633     }
634 
635     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)636     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
637         pw.println("mSystemColors=" + mCurrentColors);
638         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
639         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
640         pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
641         pw.println("mNeutralOverlay=" + mNeutralOverlay);
642         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
643         pw.println("mColorScheme=" + mColorScheme);
644         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
645         pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
646         pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation);
647     }
648 }
649