1 /*
2  * Copyright (C) 2017 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 
17 package com.android.internal.colorextraction;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.WallpaperColors;
22 import android.app.WallpaperManager;
23 import android.content.Context;
24 import android.os.AsyncTask;
25 import android.util.Log;
26 import android.util.SparseArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.colorextraction.types.ExtractionType;
30 import com.android.internal.colorextraction.types.Tonal;
31 
32 import java.lang.ref.WeakReference;
33 import java.util.ArrayList;
34 
35 /**
36  * Class to process wallpaper colors and generate a tonal palette based on them.
37  */
38 public class ColorExtractor implements WallpaperManager.OnColorsChangedListener {
39 
40     public static final int TYPE_NORMAL = 0;
41     public static final int TYPE_DARK = 1;
42     public static final int TYPE_EXTRA_DARK = 2;
43     private static final int[] sGradientTypes = new int[]{TYPE_NORMAL, TYPE_DARK, TYPE_EXTRA_DARK};
44 
45     private static final String TAG = "ColorExtractor";
46     private static final boolean DEBUG = false;
47 
48     protected final SparseArray<GradientColors[]> mGradientColors;
49     private final ArrayList<WeakReference<OnColorsChangedListener>> mOnColorsChangedListeners;
50     private final Context mContext;
51     private final ExtractionType mExtractionType;
52     protected WallpaperColors mSystemColors;
53     protected WallpaperColors mLockColors;
54 
ColorExtractor(Context context)55     public ColorExtractor(Context context) {
56         this(context, new Tonal(context), true /* immediately */,
57                 context.getSystemService(WallpaperManager.class));
58     }
59 
60     @VisibleForTesting
ColorExtractor(Context context, ExtractionType extractionType, boolean immediately, WallpaperManager wallpaperManager)61     public ColorExtractor(Context context, ExtractionType extractionType, boolean immediately,
62             WallpaperManager wallpaperManager) {
63         mContext = context;
64         mExtractionType = extractionType;
65 
66         mGradientColors = new SparseArray<>();
67         for (int which : new int[] { WallpaperManager.FLAG_LOCK, WallpaperManager.FLAG_SYSTEM}) {
68             GradientColors[] colors = new GradientColors[sGradientTypes.length];
69             mGradientColors.append(which, colors);
70             for (int type : sGradientTypes) {
71                 colors[type] = new GradientColors();
72             }
73         }
74 
75         mOnColorsChangedListeners = new ArrayList<>();
76         if (wallpaperManager.isWallpaperSupported()) {
77             wallpaperManager.addOnColorsChangedListener(this, null /* handler */);
78             initExtractColors(wallpaperManager, immediately);
79         }
80     }
81 
initExtractColors(WallpaperManager wallpaperManager, boolean immediately)82     private void initExtractColors(WallpaperManager wallpaperManager, boolean immediately) {
83         if (immediately) {
84             mSystemColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
85             mLockColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK);
86             extractWallpaperColors();
87         } else {
88             new LoadWallpaperColors().executeOnExecutor(
89                     AsyncTask.THREAD_POOL_EXECUTOR, wallpaperManager);
90         }
91     }
92 
93     private class LoadWallpaperColors extends AsyncTask<WallpaperManager, Void, Void> {
94         private WallpaperColors mSystemColors;
95         private WallpaperColors mLockColors;
96         @Override
doInBackground(WallpaperManager... params)97         protected Void doInBackground(WallpaperManager... params) {
98             mSystemColors = params[0].getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
99             mLockColors = params[0].getWallpaperColors(WallpaperManager.FLAG_LOCK);
100             return null;
101         }
102         @Override
onPostExecute(Void b)103         protected void onPostExecute(Void b) {
104             ColorExtractor.this.mSystemColors = mSystemColors;
105             ColorExtractor.this.mLockColors = mLockColors;
106             extractWallpaperColors();
107             triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
108         }
109     }
110 
extractWallpaperColors()111     protected void extractWallpaperColors() {
112         GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
113         GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
114         extractInto(mSystemColors,
115                 systemColors[TYPE_NORMAL],
116                 systemColors[TYPE_DARK],
117                 systemColors[TYPE_EXTRA_DARK]);
118         extractInto(mLockColors,
119                 lockColors[TYPE_NORMAL],
120                 lockColors[TYPE_DARK],
121                 lockColors[TYPE_EXTRA_DARK]);
122     }
123 
124     /**
125      * Retrieve gradient colors for a specific wallpaper.
126      *
127      * @param which FLAG_LOCK or FLAG_SYSTEM
128      * @return colors
129      */
130     @NonNull
getColors(int which)131     public GradientColors getColors(int which) {
132         return getColors(which, TYPE_DARK);
133     }
134 
135     /**
136      * Get current gradient colors for one of the possible gradient types
137      *
138      * @param which FLAG_LOCK or FLAG_SYSTEM
139      * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
140      * @return colors
141      */
142     @NonNull
getColors(int which, int type)143     public GradientColors getColors(int which, int type) {
144         if (type != TYPE_NORMAL && type != TYPE_DARK && type != TYPE_EXTRA_DARK) {
145             throw new IllegalArgumentException(
146                     "type should be TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK");
147         }
148         if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) {
149             throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL");
150         }
151         return mGradientColors.get(which)[type];
152     }
153 
154     /**
155      * Get the last available WallpaperColors without forcing new extraction.
156      *
157      * @param which FLAG_LOCK or FLAG_SYSTEM
158      * @return Last cached colors
159      */
160     @Nullable
getWallpaperColors(int which)161     public WallpaperColors getWallpaperColors(int which) {
162         if (which == WallpaperManager.FLAG_LOCK) {
163             return mLockColors;
164         } else if (which == WallpaperManager.FLAG_SYSTEM) {
165             return mSystemColors;
166         } else {
167             throw new IllegalArgumentException("Invalid value for which: " + which);
168         }
169     }
170 
171     @Override
onColorsChanged(WallpaperColors colors, int which)172     public void onColorsChanged(WallpaperColors colors, int which) {
173         if (DEBUG) {
174             Log.d(TAG, "New wallpaper colors for " + which + ": " + colors);
175         }
176         boolean changed = false;
177         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
178             mLockColors = colors;
179             GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
180             extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK],
181                     lockColors[TYPE_EXTRA_DARK]);
182             changed = true;
183         }
184         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
185             mSystemColors = colors;
186             GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
187             extractInto(colors, systemColors[TYPE_NORMAL], systemColors[TYPE_DARK],
188                     systemColors[TYPE_EXTRA_DARK]);
189             changed = true;
190         }
191 
192         if (changed) {
193             triggerColorsChanged(which);
194         }
195     }
196 
triggerColorsChanged(int which)197     protected void triggerColorsChanged(int which) {
198         ArrayList<WeakReference<OnColorsChangedListener>> references =
199                 new ArrayList<>(mOnColorsChangedListeners);
200         final int size = references.size();
201         for (int i = 0; i < size; i++) {
202             final WeakReference<OnColorsChangedListener> weakReference = references.get(i);
203             final OnColorsChangedListener listener = weakReference.get();
204             if (listener == null) {
205                 mOnColorsChangedListeners.remove(weakReference);
206             } else {
207                 listener.onColorsChanged(this, which);
208             }
209         }
210     }
211 
extractInto(WallpaperColors inWallpaperColors, GradientColors outGradientColorsNormal, GradientColors outGradientColorsDark, GradientColors outGradientColorsExtraDark)212     private void extractInto(WallpaperColors inWallpaperColors,
213             GradientColors outGradientColorsNormal, GradientColors outGradientColorsDark,
214             GradientColors outGradientColorsExtraDark) {
215         mExtractionType.extractInto(inWallpaperColors, outGradientColorsNormal,
216                 outGradientColorsDark, outGradientColorsExtraDark);
217     }
218 
destroy()219     public void destroy() {
220         WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);
221         if (wallpaperManager != null) {
222             wallpaperManager.removeOnColorsChangedListener(this);
223         }
224     }
225 
addOnColorsChangedListener(@onNull OnColorsChangedListener listener)226     public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
227         mOnColorsChangedListeners.add(new WeakReference<>(listener));
228     }
229 
removeOnColorsChangedListener(@onNull OnColorsChangedListener listener)230     public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
231         ArrayList<WeakReference<OnColorsChangedListener>> references =
232                 new ArrayList<>(mOnColorsChangedListeners);
233         final int size = references.size();
234         for (int i = 0; i < size; i++) {
235             final WeakReference<OnColorsChangedListener> weakReference = references.get(i);
236             if (weakReference.get() == listener) {
237                 mOnColorsChangedListeners.remove(weakReference);
238                 break;
239             }
240         }
241     }
242 
243     public static class GradientColors {
244         private int mMainColor;
245         private int mSecondaryColor;
246         private int[] mColorPalette;
247         private boolean mSupportsDarkText;
248 
setMainColor(int mainColor)249         public void setMainColor(int mainColor) {
250             mMainColor = mainColor;
251         }
252 
setSecondaryColor(int secondaryColor)253         public void setSecondaryColor(int secondaryColor) {
254             mSecondaryColor = secondaryColor;
255         }
256 
setColorPalette(int[] colorPalette)257         public void setColorPalette(int[] colorPalette) {
258             mColorPalette = colorPalette;
259         }
260 
setSupportsDarkText(boolean supportsDarkText)261         public void setSupportsDarkText(boolean supportsDarkText) {
262             mSupportsDarkText = supportsDarkText;
263         }
264 
set(GradientColors other)265         public void set(GradientColors other) {
266             mMainColor = other.mMainColor;
267             mSecondaryColor = other.mSecondaryColor;
268             mColorPalette = other.mColorPalette;
269             mSupportsDarkText = other.mSupportsDarkText;
270         }
271 
getMainColor()272         public int getMainColor() {
273             return mMainColor;
274         }
275 
getSecondaryColor()276         public int getSecondaryColor() {
277             return mSecondaryColor;
278         }
279 
getColorPalette()280         public int[] getColorPalette() {
281             return mColorPalette;
282         }
283 
supportsDarkText()284         public boolean supportsDarkText() {
285             return mSupportsDarkText;
286         }
287 
288         @Override
equals(Object o)289         public boolean equals(Object o) {
290             if (o == null || o.getClass() != getClass()) {
291                 return false;
292             }
293             GradientColors other = (GradientColors) o;
294             return other.mMainColor == mMainColor &&
295                     other.mSecondaryColor == mSecondaryColor &&
296                     other.mSupportsDarkText == mSupportsDarkText;
297         }
298 
299         @Override
hashCode()300         public int hashCode() {
301             int code = mMainColor;
302             code = 31 * code + mSecondaryColor;
303             code = 31 * code + (mSupportsDarkText ? 0 : 1);
304             return code;
305         }
306 
307         @Override
toString()308         public String toString() {
309             return "GradientColors(" + Integer.toHexString(mMainColor) + ", "
310                     + Integer.toHexString(mSecondaryColor) + ")";
311         }
312     }
313 
314     public interface OnColorsChangedListener {
onColorsChanged(ColorExtractor colorExtractor, int which)315         void onColorsChanged(ColorExtractor colorExtractor, int which);
316     }
317 }
318