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