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