blob: f0da0d5decd714af9e868467bd3e4bc764473321 [file] [log] [blame]
/*
* 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<>();
if (wallpaperManager.isWallpaperSupported()) {
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);
}
}
protected 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);
}
}