• 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 
21 import android.content.Context;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 import androidx.annotation.ColorInt;
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.customization.model.CustomizationManager;
29 import com.android.customization.model.CustomizationOption;
30 import com.android.customization.model.color.ColorOptionsProvider.ColorSource;
31 import com.android.customization.module.logging.ThemesUserEventLogger;
32 import com.android.systemui.monet.Style;
33 import com.android.themepicker.R;
34 
35 import org.json.JSONException;
36 import org.json.JSONObject;
37 
38 import java.util.Collections;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 
46 /**
47  * Represents a color choice for the user.
48  * This could be a preset color or those obtained from a wallpaper.
49  */
50 public abstract class ColorOption implements CustomizationOption<ColorOption> {
51 
52     private static final String TAG = "ColorOption";
53     private static final String EMPTY_JSON = "{}";
54     @VisibleForTesting
55     static final String TIMESTAMP_FIELD = "_applied_timestamp";
56 
57     protected final Map<String, String> mPackagesByCategory;
58     private final String mTitle;
59     private final boolean mIsDefault;
60     @Style.Type
61     private final Integer mStyle;
62     private final int mIndex;
63     private CharSequence mContentDescription;
64     private final @ColorInt int mSeedColor;
65 
ColorOption(String title, Map<String, String> overlayPackages, boolean isDefault, int seedColor, @Style.Type Integer style, int index)66     protected ColorOption(String title, Map<String, String> overlayPackages, boolean isDefault,
67             int seedColor, @Style.Type Integer style, int index) {
68         mTitle = title;
69         mIsDefault = isDefault;
70         mSeedColor = seedColor;
71         mStyle = style;
72         mIndex = index;
73         mPackagesByCategory = Collections.unmodifiableMap(removeNullValues(overlayPackages));
74     }
75 
76     @Override
getTitle()77     public String getTitle() {
78         return mTitle;
79     }
80 
81     @Override
isActive(CustomizationManager<ColorOption> manager)82     public boolean isActive(CustomizationManager<ColorOption> manager) {
83         ColorCustomizationManager colorManager = (ColorCustomizationManager) manager;
84 
85         String currentStyle = colorManager.getCurrentStyle();
86         if (TextUtils.isEmpty(currentStyle)) {
87             currentStyle = Style.toString(Style.TONAL_SPOT);
88         }
89         boolean isCurrentStyle = TextUtils.equals(Style.toString(getStyle()), currentStyle);
90 
91         if (mIsDefault) {
92             String serializedOverlays = colorManager.getStoredOverlays();
93             // a default color option is active if the manager has no stored overlays or current
94             // overlays, or the stored overlay does not contain either category system palette or
95             // category color
96             return (TextUtils.isEmpty(serializedOverlays) || EMPTY_JSON.equals(serializedOverlays)
97                     || colorManager.getCurrentOverlays().isEmpty() || !(serializedOverlays.contains(
98                     OVERLAY_CATEGORY_SYSTEM_PALETTE) || serializedOverlays.contains(
99                     OVERLAY_CATEGORY_COLOR))) && isCurrentStyle;
100         } else {
101             Map<String, String> currentOverlays = colorManager.getCurrentOverlays();
102             String currentSource = colorManager.getCurrentColorSource();
103             boolean isCurrentSource = TextUtils.isEmpty(currentSource) || getSource().equals(
104                     currentSource);
105             return isCurrentSource && isCurrentStyle && mPackagesByCategory.equals(currentOverlays);
106         }
107     }
108 
getSeedColor()109     public @ColorInt int getSeedColor() {
110         return mSeedColor;
111     }
112 
113     /**
114      * This is similar to #equals() but it only compares this theme's packages with the other, that
115      * is, it will return true if applying this theme has the same effect of applying the given one.
116      */
isEquivalent(ColorOption other)117     public boolean isEquivalent(ColorOption other) {
118         if (other == null) {
119             return false;
120         }
121         if (!Objects.equals(mStyle, other.getStyle())) {
122             return false;
123         }
124         String thisSerializedPackages = getSerializedPackages();
125         if (mIsDefault || TextUtils.isEmpty(thisSerializedPackages)
126                 || EMPTY_JSON.equals(thisSerializedPackages)) {
127             String otherSerializedPackages = other.getSerializedPackages();
128             return other.isDefault() || TextUtils.isEmpty(otherSerializedPackages)
129                     || EMPTY_JSON.equals(otherSerializedPackages);
130         }
131         // Map#equals ensures keys and values are compared.
132         return mPackagesByCategory.equals(other.mPackagesByCategory);
133     }
134 
135     /**
136      * Returns the {@link PreviewInfo} object for this ColorOption
137      */
getPreviewInfo()138     public abstract PreviewInfo getPreviewInfo();
139 
isDefault()140     boolean isDefault() {
141         return mIsDefault;
142     }
143 
getPackagesByCategory()144     public Map<String, String> getPackagesByCategory() {
145         return mPackagesByCategory;
146     }
147 
getSerializedPackages()148     public String getSerializedPackages() {
149         return getJsonPackages(false).toString();
150     }
151 
getSerializedPackagesWithTimestamp()152     public String getSerializedPackagesWithTimestamp() {
153         return getJsonPackages(true).toString();
154     }
155 
156     /**
157      * Get a JSONObject representation of this color option, with the current values for each
158      * field, and optionally a {@link TIMESTAMP_FIELD} field.
159      * @param insertTimestamp whether to add a field with the current timestamp
160      * @return the JSONObject for this color option
161      */
getJsonPackages(boolean insertTimestamp)162     public JSONObject getJsonPackages(boolean insertTimestamp) {
163         JSONObject json;
164         if (isDefault()) {
165             json = new JSONObject();
166         } else {
167             json = new JSONObject(mPackagesByCategory);
168             // Remove items with null values to avoid deserialization issues.
169             removeNullValues(json);
170         }
171         if (insertTimestamp) {
172             try {
173                 json.put(TIMESTAMP_FIELD, System.currentTimeMillis());
174             } catch (JSONException e) {
175                 Log.e(TAG, "Couldn't add timestamp to serialized themebundle");
176             }
177         }
178         return json;
179     }
180 
removeNullValues(JSONObject json)181     private void removeNullValues(JSONObject json) {
182         Iterator<String> keys = json.keys();
183         Set<String> keysToRemove = new HashSet<>();
184         while (keys.hasNext()) {
185             String key = keys.next();
186             if (json.isNull(key)) {
187                 keysToRemove.add(key);
188             }
189         }
190         for (String key : keysToRemove) {
191             json.remove(key);
192         }
193     }
194 
removeNullValues(Map<String, String> map)195     private Map<String, String> removeNullValues(Map<String, String> map) {
196         return map.entrySet()
197                 .stream()
198                 .filter(entry -> entry.getValue() != null)
199                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
200     }
201 
202     /** */
getContentDescription(Context context)203     public CharSequence getContentDescription(Context context) {
204         if (mContentDescription == null) {
205             CharSequence defaultName = context.getString(R.string.default_theme_title);
206             if (isDefault()) {
207                 mContentDescription = defaultName;
208             } else {
209                 mContentDescription = mTitle;
210             }
211         }
212         return mContentDescription;
213     }
214 
215     /**
216      * @return the source of this color option
217      */
218     @ColorSource
getSource()219     public abstract String getSource();
220 
221     /**
222      * @return the source of this color option for logging
223      */
224     @ThemesUserEventLogger.ColorSource
getSourceForLogging()225     public abstract int getSourceForLogging();
226 
227     /**
228      * @return the style of this color option
229      */
230     @Style.Type
getStyle()231     public Integer getStyle() {
232         return mStyle;
233     }
234 
235     /**
236      * @return the style of this color option for logging
237      */
getStyleForLogging()238     public abstract int getStyleForLogging();
239 
240     /**
241      * @return the index of this color option
242      */
getIndex()243     public int getIndex() {
244         return mIndex;
245     }
246 
247     /**
248      * The preview information of {@link ColorOption}
249      */
250     public interface PreviewInfo {
251     }
252 
253 }
254