From 462b5cca7bb1d5e24fb8277b0cfe238cc2e1e980 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 1 Apr 2016 16:00:49 -0700 Subject: Extract color for the hotseat. - Only considers the bottom fourth of the wallpaper - Is translucent black or white depending on how dark/light the wallpaper is - Hotseat extends behind the nav bar Bug: 27230217 Change-Id: Id4ea6ee91b4dd221b4c277d22d5041cab178801d --- build.gradle | 2 +- src/com/android/launcher3/Hotseat.java | 59 ++++++++++++++- src/com/android/launcher3/Launcher.java | 5 +- src/com/android/launcher3/Utilities.java | 1 - .../dynamicui/ColorExtractionService.java | 14 ++++ .../launcher3/dynamicui/ExtractedColors.java | 86 ++++++++++++++-------- .../launcher3/dynamicui/ExtractionUtils.java | 44 ++++++++++- 7 files changed, 176 insertions(+), 35 deletions(-) diff --git a/build.gradle b/build.gradle index 4df406363..3cd278df4 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.1.0-rc1' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.0' } } diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index bb70be697..7e1ecf5af 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -16,8 +16,12 @@ package com.android.launcher3; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -27,12 +31,13 @@ import android.view.ViewDebug; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; public class Hotseat extends FrameLayout - implements UserEventDispatcher.LaunchSourceProvider{ + implements UserEventDispatcher.LaunchSourceProvider, Insettable { private CellLayout mContent; @@ -44,6 +49,14 @@ public class Hotseat extends FrameLayout @ViewDebug.ExportedProperty(category = "launcher") private final boolean mHasVerticalHotseat; + @ViewDebug.ExportedProperty(category = "launcher") + private Rect mInsets = new Rect(); + + @ViewDebug.ExportedProperty(category = "launcher") + private int mBackgroundColor; + @ViewDebug.ExportedProperty(category = "launcher") + private ColorDrawable mBackground; + public Hotseat(Context context) { this(context, null); } @@ -56,6 +69,8 @@ public class Hotseat extends FrameLayout super(context, attrs, defStyle); mLauncher = (Launcher) context; mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout(); + mBackground = new ColorDrawable(); + setBackground(mBackground); } public CellLayout getLayout() { @@ -166,4 +181,46 @@ public class Hotseat extends FrameLayout target.gridY = info.cellY; targetParent.containerType = LauncherLogProto.HOTSEAT; } + + //Overridden so that the background color extends behind the navigation buttons. + @Override + public void setInsets(Rect insets) { + int rightInset = insets.right - mInsets.right; + int bottomInset = insets.bottom - mInsets.bottom; + mInsets.set(insets); + LayoutParams lp = (LayoutParams) getLayoutParams(); + if (mHasVerticalHotseat) { + setPadding(getPaddingLeft(), getPaddingTop(), + getPaddingRight() + rightInset, getPaddingBottom()); + if (lp.width != LayoutParams.MATCH_PARENT && lp.width != LayoutParams.WRAP_CONTENT) { + lp.width += rightInset; + } + } else { + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), + getPaddingBottom() + bottomInset); + if (lp.height != LayoutParams.MATCH_PARENT && lp.height != LayoutParams.WRAP_CONTENT) { + lp.height += bottomInset; + } + } + } + + public void updateColor(ExtractedColors extractedColors, boolean animate) { + if (!mHasVerticalHotseat) { + int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT); + if (!animate) { + setBackgroundColor(color); + } else { + ValueAnimator animator = ValueAnimator.ofInt(mBackgroundColor, color); + animator.setEvaluator(new ArgbEvaluator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mBackground.setColor((Integer) animation.getAnimatedValue()); + } + }); + animator.start(); + } + mBackgroundColor = color; + } + } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 21adcb7e1..2a8329934 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -496,9 +496,10 @@ public class Launcher extends Activity } private void loadExtractedColorsAndColorItems() { - if (mExtractedColors != null) { + // TODO: do this in pre-N as well, once the extraction part is complete. + if (mExtractedColors != null && Utilities.isNycOrAbove()) { mExtractedColors.load(this); - // TODO: pass mExtractedColors to interested items such as hotseat. + mHotseat.updateColor(mExtractedColors, !mPaused); } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 871f39045..c5f601dcd 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -46,7 +46,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; import android.os.Build; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.PowerManager; import android.text.Spannable; diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java index 95a62b957..89594f407 100644 --- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java +++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java @@ -32,6 +32,9 @@ import com.android.launcher3.LauncherSettings; */ public class ColorExtractionService extends IntentService { + /** The fraction of the wallpaper to extract colors for use on the hotseat. */ + private static final float HOTSEAT_FRACTION = 1f / 4; + public ColorExtractionService() { super("ColorExtractionService"); } @@ -44,10 +47,21 @@ public class ColorExtractionService extends IntentService { if (wallpaperManager.getWallpaperInfo() != null) { // We can't extract colors from live wallpapers, so just use the default color always. extractedColors.updatePalette(null); + extractedColors.updateHotseatPalette(null); } else { Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); Palette palette = Palette.from(wallpaper).generate(); extractedColors.updatePalette(palette); + // We extract colors for the hotseat separately, + // since it only considers the lower part of the wallpaper. + // TODO(twickham): update Palette library to 23.3.1 or higher, + // which fixes a bug with using regions (b/28349435). + Palette hotseatPalette = Palette.from(wallpaper) + .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)), + wallpaper.getWidth(), wallpaper.getHeight()) + .clearFilters() + .generate(); + extractedColors.updateHotseatPalette(hotseatPalette); } // Save the extracted colors and wallpaper id to LauncherProvider. diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java index 4d17ff764..e545288a0 100644 --- a/src/com/android/launcher3/dynamicui/ExtractedColors.java +++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java @@ -18,6 +18,7 @@ package com.android.launcher3.dynamicui; import android.content.Context; import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; import android.support.v7.graphics.Palette; import android.util.Log; @@ -35,26 +36,30 @@ public class ExtractedColors { // These color profile indices should NOT be changed, since they are used when saving and // loading extracted colors. New colors should always be added at the end. - public static final int DEFAULT_INDEX = 0; - public static final int VIBRANT_INDEX = 1; - public static final int VIBRANT_DARK_INDEX = 2; - public static final int VIBRANT_LIGHT_INDEX = 3; - public static final int MUTED_INDEX = 4; - public static final int MUTED_DARK_INDEX = 5; - public static final int MUTED_LIGHT_INDEX = 6; - - public static final int NUM_COLOR_PROFILES = 7; + public static final int VERSION_INDEX = 0; + public static final int HOTSEAT_INDEX = 1; + // public static final int VIBRANT_INDEX = 2; + // public static final int VIBRANT_DARK_INDEX = 3; + // public static final int VIBRANT_LIGHT_INDEX = 4; + // public static final int MUTED_INDEX = 5; + // public static final int MUTED_DARK_INDEX = 6; + // public static final int MUTED_LIGHT_INDEX = 7; + + public static final int NUM_COLOR_PROFILES = 1; + private static final int VERSION = 1; private static final String COLOR_SEPARATOR = ","; private int[] mColors; public ExtractedColors() { - mColors = new int[NUM_COLOR_PROFILES]; + // The first entry is reserved for the version number. + mColors = new int[NUM_COLOR_PROFILES + 1]; + mColors[VERSION_INDEX] = VERSION; } public void setColorAtIndex(int index, int color) { - if (index >= 0 && index < mColors.length) { + if (index > VERSION_INDEX && index < mColors.length) { mColors[index] = color; } else { Log.e(TAG, "Attempted to set a color at an invalid index " + index); @@ -89,17 +94,21 @@ public class ExtractedColors { */ public void load(Context context) { String encodedString = Utilities.getPrefs(context).getString( - ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, DEFAULT_COLOR + ""); + ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, VERSION + ""); decodeFromString(encodedString); + + if (mColors[VERSION_INDEX] != VERSION) { + ExtractionUtils.startColorExtractionService(context); + } } /** @param index must be one of the index values defined at the top of this class. */ - public int getColor(int index) { - if (index >= 0 && index < mColors.length) { + public int getColor(int index, int defaultColor) { + if (index > VERSION_INDEX && index < mColors.length) { return mColors[index]; } - return DEFAULT_COLOR; + return defaultColor; } /** @@ -112,20 +121,39 @@ public class ExtractedColors { setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR); } } else { - setColorAtIndex(ExtractedColors.DEFAULT_INDEX, - ExtractedColors.DEFAULT_COLOR); - setColorAtIndex(ExtractedColors.VIBRANT_INDEX, - palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR)); - setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX, - palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK)); - setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX, - palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); - setColorAtIndex(ExtractedColors.MUTED_INDEX, - palette.getMutedColor(ExtractedColors.DEFAULT_COLOR)); - setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX, - palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK)); - setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX, - palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + // We currently don't use any of the colors defined by the Palette API, + // but this is how we would add them if we ever need them. + + // setColorAtIndex(ExtractedColors.VIBRANT_INDEX, + // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR)); + // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX, + // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK)); + // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX, + // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + // setColorAtIndex(ExtractedColors.MUTED_INDEX, + // palette.getMutedColor(DEFAULT_COLOR)); + // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX, + // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK)); + // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX, + // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); + } + } + + /** + * The hotseat's color is defined as follows: + * - 12% black for super light wallpaper + * - 18% white for super dark + * - 25% white otherwise + */ + public void updateHotseatPalette(Palette hotseatPalette) { + int hotseatColor; + if (hotseatPalette != null && ExtractionUtils.isSuperLight(hotseatPalette)) { + hotseatColor = ColorUtils.setAlphaComponent(Color.BLACK, (int) (0.12f * 255)); + } else if (hotseatPalette != null && ExtractionUtils.isSuperDark(hotseatPalette)) { + hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.18f * 255)); + } else { + hotseatColor = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.25f * 255)); } + setColorAtIndex(HOTSEAT_INDEX, hotseatColor); } } diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java index 0b28ba6f9..6dc0035ee 100644 --- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java +++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java @@ -20,11 +20,15 @@ import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Color; +import android.support.v4.graphics.ColorUtils; +import android.support.v7.graphics.Palette; import com.android.launcher3.Utilities; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.List; /** * Contains helper fields and methods related to extracting colors from the wallpaper. @@ -34,6 +38,7 @@ public class ExtractionUtils { public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId"; private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM + private static final float MIN_CONTRAST_RATIO = 2f; /** * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed. @@ -46,12 +51,17 @@ public class ExtractionUtils { @Override public void run() { if (hasWallpaperIdChanged(context)) { - context.startService(new Intent(context, ColorExtractionService.class)); + startColorExtractionService(context); } } }); } + /** Starts the {@link ColorExtractionService} without checking the wallpaper id */ + public static void startColorExtractionService(Context context) { + context.startService(new Intent(context, ColorExtractionService.class)); + } + private static boolean hasWallpaperIdChanged(Context context) { if (!Utilities.isNycOrAbove()) { // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here. @@ -72,4 +82,36 @@ public class ExtractionUtils { return -1; } } + + public static boolean isSuperLight(Palette p) { + return !isLegibleOnWallpaper(Color.WHITE, p.getSwatches()); + } + + public static boolean isSuperDark(Palette p) { + return !isLegibleOnWallpaper(Color.BLACK, p.getSwatches()); + } + + /** + * Given a color, returns true if that color is legible on + * the given wallpaper color swatches, else returns false. + */ + private static boolean isLegibleOnWallpaper(int color, List wallpaperSwatches) { + int legiblePopulation = 0; + int illegiblePopulation = 0; + for (Palette.Swatch swatch : wallpaperSwatches) { + if (isLegible(color, swatch.getRgb())) { + legiblePopulation += swatch.getPopulation(); + } else { + illegiblePopulation += swatch.getPopulation(); + } + } + return legiblePopulation > illegiblePopulation; + } + + /** @return Whether the foreground color is legible on the background color. */ + private static boolean isLegible(int foreground, int background) { + background = ColorUtils.setAlphaComponent(background, 255); + return ColorUtils.calculateContrast(foreground, background) >= MIN_CONTRAST_RATIO; + } + } -- cgit v1.2.3