| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.internal.colorextraction; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.WallpaperColors; |
| import android.app.WallpaperManager; |
| import android.content.Context; |
| import android.os.AsyncTask; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.colorextraction.types.ExtractionType; |
| import com.android.internal.colorextraction.types.Tonal; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| |
| /** |
| * Class to process wallpaper colors and generate a tonal palette based on them. |
| */ |
| public class ColorExtractor implements WallpaperManager.OnColorsChangedListener { |
| |
| public static final int TYPE_NORMAL = 0; |
| public static final int TYPE_DARK = 1; |
| public static final int TYPE_EXTRA_DARK = 2; |
| private static final int[] sGradientTypes = new int[]{TYPE_NORMAL, TYPE_DARK, TYPE_EXTRA_DARK}; |
| |
| private static final String TAG = "ColorExtractor"; |
| private static final boolean DEBUG = false; |
| |
| protected final SparseArray<GradientColors[]> mGradientColors; |
| private final ArrayList<WeakReference<OnColorsChangedListener>> mOnColorsChangedListeners; |
| private final Context mContext; |
| private final ExtractionType mExtractionType; |
| protected WallpaperColors mSystemColors; |
| protected WallpaperColors mLockColors; |
| |
| public ColorExtractor(Context context) { |
| this(context, new Tonal(context), true /* immediately */, |
| context.getSystemService(WallpaperManager.class)); |
| } |
| |
| @VisibleForTesting |
| public ColorExtractor(Context context, ExtractionType extractionType, boolean immediately, |
| WallpaperManager wallpaperManager) { |
| mContext = context; |
| mExtractionType = extractionType; |
| |
| mGradientColors = new SparseArray<>(); |
| for (int which : new int[] { WallpaperManager.FLAG_LOCK, WallpaperManager.FLAG_SYSTEM}) { |
| GradientColors[] colors = new GradientColors[sGradientTypes.length]; |
| mGradientColors.append(which, colors); |
| for (int type : sGradientTypes) { |
| colors[type] = new GradientColors(); |
| } |
| } |
| |
| mOnColorsChangedListeners = new ArrayList<>(); |
| wallpaperManager.addOnColorsChangedListener(this, null /* handler */); |
| initExtractColors(wallpaperManager, immediately); |
| } |
| |
| private void initExtractColors(WallpaperManager wallpaperManager, boolean immediately) { |
| if (immediately) { |
| mSystemColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM); |
| mLockColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK); |
| extractWallpaperColors(); |
| } else { |
| new LoadWallpaperColors().executeOnExecutor( |
| AsyncTask.THREAD_POOL_EXECUTOR, wallpaperManager); |
| } |
| } |
| |
| private class LoadWallpaperColors extends AsyncTask<WallpaperManager, Void, Void> { |
| private WallpaperColors mSystemColors; |
| private WallpaperColors mLockColors; |
| @Override |
| protected Void doInBackground(WallpaperManager... params) { |
| mSystemColors = params[0].getWallpaperColors(WallpaperManager.FLAG_SYSTEM); |
| mLockColors = params[0].getWallpaperColors(WallpaperManager.FLAG_LOCK); |
| return null; |
| } |
| @Override |
| protected void onPostExecute(Void b) { |
| ColorExtractor.this.mSystemColors = mSystemColors; |
| ColorExtractor.this.mLockColors = mLockColors; |
| extractWallpaperColors(); |
| triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK); |
| } |
| } |
| |
| private void extractWallpaperColors() { |
| GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM); |
| GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK); |
| extractInto(mSystemColors, |
| systemColors[TYPE_NORMAL], |
| systemColors[TYPE_DARK], |
| systemColors[TYPE_EXTRA_DARK]); |
| extractInto(mLockColors, |
| lockColors[TYPE_NORMAL], |
| lockColors[TYPE_DARK], |
| lockColors[TYPE_EXTRA_DARK]); |
| } |
| |
| /** |
| * Retrieve gradient colors for a specific wallpaper. |
| * |
| * @param which FLAG_LOCK or FLAG_SYSTEM |
| * @return colors |
| */ |
| @NonNull |
| public GradientColors getColors(int which) { |
| return getColors(which, TYPE_DARK); |
| } |
| |
| /** |
| * Get current gradient colors for one of the possible gradient types |
| * |
| * @param which FLAG_LOCK or FLAG_SYSTEM |
| * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK |
| * @return colors |
| */ |
| @NonNull |
| public GradientColors getColors(int which, int type) { |
| if (type != TYPE_NORMAL && type != TYPE_DARK && type != TYPE_EXTRA_DARK) { |
| throw new IllegalArgumentException( |
| "type should be TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK"); |
| } |
| if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) { |
| throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL"); |
| } |
| return mGradientColors.get(which)[type]; |
| } |
| |
| /** |
| * Get the last available WallpaperColors without forcing new extraction. |
| * |
| * @param which FLAG_LOCK or FLAG_SYSTEM |
| * @return Last cached colors |
| */ |
| @Nullable |
| public WallpaperColors getWallpaperColors(int which) { |
| if (which == WallpaperManager.FLAG_LOCK) { |
| return mLockColors; |
| } else if (which == WallpaperManager.FLAG_SYSTEM) { |
| return mSystemColors; |
| } else { |
| throw new IllegalArgumentException("Invalid value for which: " + which); |
| } |
| } |
| |
| @Override |
| public void onColorsChanged(WallpaperColors colors, int which) { |
| if (DEBUG) { |
| Log.d(TAG, "New wallpaper colors for " + which + ": " + colors); |
| } |
| boolean changed = false; |
| if ((which & WallpaperManager.FLAG_LOCK) != 0) { |
| mLockColors = colors; |
| GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK); |
| extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK], |
| lockColors[TYPE_EXTRA_DARK]); |
| changed = true; |
| } |
| if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { |
| mSystemColors = colors; |
| GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM); |
| extractInto(colors, systemColors[TYPE_NORMAL], systemColors[TYPE_DARK], |
| systemColors[TYPE_EXTRA_DARK]); |
| changed = true; |
| } |
| |
| if (changed) { |
| triggerColorsChanged(which); |
| } |
| } |
| |
| protected void triggerColorsChanged(int which) { |
| ArrayList<WeakReference<OnColorsChangedListener>> references = |
| new ArrayList<>(mOnColorsChangedListeners); |
| final int size = references.size(); |
| for (int i = 0; i < size; i++) { |
| final WeakReference<OnColorsChangedListener> weakReference = references.get(i); |
| final OnColorsChangedListener listener = weakReference.get(); |
| if (listener == null) { |
| mOnColorsChangedListeners.remove(weakReference); |
| } else { |
| listener.onColorsChanged(this, which); |
| } |
| } |
| } |
| |
| private void extractInto(WallpaperColors inWallpaperColors, |
| GradientColors outGradientColorsNormal, GradientColors outGradientColorsDark, |
| GradientColors outGradientColorsExtraDark) { |
| mExtractionType.extractInto(inWallpaperColors, outGradientColorsNormal, |
| outGradientColorsDark, outGradientColorsExtraDark); |
| } |
| |
| public void destroy() { |
| WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class); |
| if (wallpaperManager != null) { |
| wallpaperManager.removeOnColorsChangedListener(this); |
| } |
| } |
| |
| public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) { |
| mOnColorsChangedListeners.add(new WeakReference<>(listener)); |
| } |
| |
| public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener listener) { |
| ArrayList<WeakReference<OnColorsChangedListener>> references = |
| new ArrayList<>(mOnColorsChangedListeners); |
| final int size = references.size(); |
| for (int i = 0; i < size; i++) { |
| final WeakReference<OnColorsChangedListener> weakReference = references.get(i); |
| if (weakReference.get() == listener) { |
| mOnColorsChangedListeners.remove(weakReference); |
| break; |
| } |
| } |
| } |
| |
| public static class GradientColors { |
| private int mMainColor; |
| private int mSecondaryColor; |
| private int[] mColorPalette; |
| private boolean mSupportsDarkText; |
| |
| public void setMainColor(int mainColor) { |
| mMainColor = mainColor; |
| } |
| |
| public void setSecondaryColor(int secondaryColor) { |
| mSecondaryColor = secondaryColor; |
| } |
| |
| public void setColorPalette(int[] colorPalette) { |
| mColorPalette = colorPalette; |
| } |
| |
| public void setSupportsDarkText(boolean supportsDarkText) { |
| mSupportsDarkText = supportsDarkText; |
| } |
| |
| public void set(GradientColors other) { |
| mMainColor = other.mMainColor; |
| mSecondaryColor = other.mSecondaryColor; |
| mColorPalette = other.mColorPalette; |
| mSupportsDarkText = other.mSupportsDarkText; |
| } |
| |
| public int getMainColor() { |
| return mMainColor; |
| } |
| |
| public int getSecondaryColor() { |
| return mSecondaryColor; |
| } |
| |
| public int[] getColorPalette() { |
| return mColorPalette; |
| } |
| |
| public boolean supportsDarkText() { |
| return mSupportsDarkText; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o == null || o.getClass() != getClass()) { |
| return false; |
| } |
| GradientColors other = (GradientColors) o; |
| return other.mMainColor == mMainColor && |
| other.mSecondaryColor == mSecondaryColor && |
| other.mSupportsDarkText == mSupportsDarkText; |
| } |
| |
| @Override |
| public int hashCode() { |
| int code = mMainColor; |
| code = 31 * code + mSecondaryColor; |
| code = 31 * code + (mSupportsDarkText ? 0 : 1); |
| return code; |
| } |
| |
| @Override |
| public String toString() { |
| return "GradientColors(" + Integer.toHexString(mMainColor) + ", " |
| + Integer.toHexString(mSecondaryColor) + ")"; |
| } |
| } |
| |
| public interface OnColorsChangedListener { |
| void onColorsChanged(ColorExtractor colorExtractor, int which); |
| } |
| } |