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.customization.model.theme; 17 18 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR; 19 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT; 20 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID; 21 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER; 22 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS; 23 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI; 24 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER; 25 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE; 26 27 import android.provider.Settings; 28 import android.text.TextUtils; 29 30 import androidx.annotation.Nullable; 31 import androidx.fragment.app.FragmentActivity; 32 33 import com.android.customization.model.CustomizationManager; 34 import com.android.customization.model.ResourceConstants; 35 import com.android.customization.model.theme.custom.CustomTheme; 36 import com.android.customization.module.ThemesUserEventLogger; 37 38 import org.json.JSONObject; 39 40 import java.util.HashSet; 41 import java.util.Map; 42 import java.util.Set; 43 44 public class ThemeManager implements CustomizationManager<ThemeBundle> { 45 46 public static final Set<String> THEME_CATEGORIES = new HashSet<>(); 47 static { 48 THEME_CATEGORIES.add(OVERLAY_CATEGORY_COLOR); 49 THEME_CATEGORIES.add(OVERLAY_CATEGORY_FONT); 50 THEME_CATEGORIES.add(OVERLAY_CATEGORY_SHAPE); 51 THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_ANDROID); 52 THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SETTINGS); 53 THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SYSUI); 54 THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_LAUNCHER); 55 THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_THEMEPICKER); 56 } 57 58 private final ThemeBundleProvider mProvider; 59 private final OverlayManagerCompat mOverlayManagerCompat; 60 61 protected final FragmentActivity mActivity; 62 private final ThemesUserEventLogger mEventLogger; 63 64 private Map<String, String> mCurrentOverlays; 65 ThemeManager(ThemeBundleProvider provider, FragmentActivity activity, OverlayManagerCompat overlayManagerCompat, ThemesUserEventLogger logger)66 public ThemeManager(ThemeBundleProvider provider, FragmentActivity activity, 67 OverlayManagerCompat overlayManagerCompat, 68 ThemesUserEventLogger logger) { 69 mProvider = provider; 70 mActivity = activity; 71 mOverlayManagerCompat = overlayManagerCompat; 72 mEventLogger = logger; 73 } 74 75 @Override isAvailable()76 public boolean isAvailable() { 77 return mOverlayManagerCompat.isAvailable() && mProvider.isAvailable(); 78 } 79 80 @Override apply(ThemeBundle theme, Callback callback)81 public void apply(ThemeBundle theme, Callback callback) { 82 applyOverlays(theme, callback); 83 } 84 applyOverlays(ThemeBundle theme, Callback callback)85 private void applyOverlays(ThemeBundle theme, Callback callback) { 86 boolean allApplied = Settings.Secure.putString(mActivity.getContentResolver(), 87 ResourceConstants.THEME_SETTING, theme.getSerializedPackagesWithTimestamp()); 88 if (theme instanceof CustomTheme) { 89 storeCustomTheme((CustomTheme) theme); 90 } 91 mCurrentOverlays = null; 92 if (allApplied) { 93 mEventLogger.logThemeApplied(theme, theme instanceof CustomTheme); 94 callback.onSuccess(); 95 } else { 96 callback.onError(null); 97 } 98 } 99 storeCustomTheme(CustomTheme theme)100 private void storeCustomTheme(CustomTheme theme) { 101 mProvider.storeCustomTheme(theme); 102 } 103 104 @Override fetchOptions(OptionsFetchedListener<ThemeBundle> callback, boolean reload)105 public void fetchOptions(OptionsFetchedListener<ThemeBundle> callback, boolean reload) { 106 mProvider.fetch(callback, reload); 107 } 108 getCurrentOverlays()109 public Map<String, String> getCurrentOverlays() { 110 if (mCurrentOverlays == null) { 111 mCurrentOverlays = mOverlayManagerCompat.getEnabledOverlaysForTargets( 112 ResourceConstants.getPackagesToOverlay(mActivity)); 113 mCurrentOverlays.entrySet().removeIf( 114 categoryAndPackage -> !THEME_CATEGORIES.contains(categoryAndPackage.getKey())); 115 } 116 return mCurrentOverlays; 117 } 118 getStoredOverlays()119 public String getStoredOverlays() { 120 return Settings.Secure.getString(mActivity.getContentResolver(), 121 ResourceConstants.THEME_SETTING); 122 } 123 removeCustomTheme(CustomTheme theme)124 public void removeCustomTheme(CustomTheme theme) { 125 mProvider.removeCustomTheme(theme); 126 } 127 128 /** 129 * @return an existing ThemeBundle that matches the same packages as the given one, if one 130 * exists, or {@code null} otherwise. 131 */ 132 @Nullable findThemeByPackages(ThemeBundle other)133 public ThemeBundle findThemeByPackages(ThemeBundle other) { 134 return mProvider.findEquivalent(other); 135 } 136 137 /** 138 * Store empty theme if no theme has been set yet. This will prevent Settings from showing the 139 * suggestion to select a theme 140 */ storeEmptyTheme()141 public void storeEmptyTheme() { 142 String themeSetting = Settings.Secure.getString(mActivity.getContentResolver(), 143 ResourceConstants.THEME_SETTING); 144 if (TextUtils.isEmpty(themeSetting)) { 145 Settings.Secure.putString(mActivity.getContentResolver(), 146 ResourceConstants.THEME_SETTING, new JSONObject().toString()); 147 } 148 } 149 } 150