summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/values/dimens.xml4
-rw-r--r--src/com/android/launcher3/BubbleTextView.java17
-rw-r--r--src/com/android/launcher3/DeviceProfile.java9
-rw-r--r--src/com/android/launcher3/FastBitmapDrawable.java30
-rw-r--r--src/com/android/launcher3/Utilities.java2
-rw-r--r--src/com/android/launcher3/Workspace.java2
-rw-r--r--src/com/android/launcher3/badge/BadgeInfo.java33
-rw-r--r--src/com/android/launcher3/badge/BadgeRenderer.java70
-rw-r--r--src/com/android/launcher3/graphics/IconPalette.java63
9 files changed, 223 insertions, 7 deletions
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1a09fa002..c7bc28641 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -169,6 +169,10 @@
also happens to equal 19dp-->
<dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
+<!-- Icon badges (with notification counts) -->
+ <dimen name="badge_size">24dp</dimen>
+ <dimen name="badge_text_size">12dp</dimen>
+
<!-- Other -->
<!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
<dimen name="status_bar_height">24dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index b8b43c9c3..77572c7f7 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -39,6 +39,8 @@ import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.HolographicOutlineHelper;
@@ -164,7 +166,7 @@ public class BubbleTextView extends TextView
applyIconAndLabel(info.iconBitmap, info);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
- applyState(promiseStateChanged);
+ applyPromiseState(promiseStateChanged);
}
}
@@ -470,7 +472,7 @@ public class BubbleTextView extends TextView
mLongPressHelper.cancelLongPress();
}
- public void applyState(boolean promiseStateChanged) {
+ public void applyPromiseState(boolean promiseStateChanged) {
if (getTag() instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) getTag();
final boolean isPromise = info.isPromise();
@@ -479,8 +481,8 @@ public class BubbleTextView extends TextView
info.getInstallProgress() : 0)) : 100;
setContentDescription(progressLevel > 0 ?
- getContext().getString(R.string.app_downloading_title, info.title,
- NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
+ getContext().getString(R.string.app_downloading_title, info.title,
+ NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
getContext().getString(R.string.app_waiting_download_title, info.title));
if (mIcon != null) {
@@ -500,6 +502,13 @@ public class BubbleTextView extends TextView
}
}
+ public void applyBadgeState(BadgeInfo badgeInfo) {
+ if (mIcon instanceof FastBitmapDrawable) {
+ BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+ ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer);
+ }
+ }
+
private Theme getPreloaderTheme() {
Object tag = getTag();
int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 27afdc03d..34ce92336 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -31,7 +31,7 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import com.android.launcher3.CellLayout.ContainerType;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.badge.BadgeRenderer;
import java.util.ArrayList;
@@ -136,6 +136,9 @@ public class DeviceProfile {
// Listeners
private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
+ // Icon badges
+ public BadgeRenderer mBadgeRenderer;
+
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
int width, int height, boolean isLandscape) {
@@ -193,6 +196,10 @@ public class DeviceProfile {
hotseatBarBottomPaddingPx = 0;
hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
+ int badgeSize = res.getDimensionPixelSize(R.dimen.badge_size);
+ int badgeTextSize = res.getDimensionPixelSize(R.dimen.badge_text_size);
+ mBadgeRenderer = new BadgeRenderer(badgeSize, badgeTextSize);
+
// Determine sizes.
widthPx = width;
heightPx = height;
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 0cefc5739..b3e59f99b 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -33,6 +33,10 @@ import android.graphics.drawable.Drawable;
import android.util.SparseArray;
import android.view.animation.DecelerateInterpolator;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badge.BadgeInfo;
+
public class FastBitmapDrawable extends Drawable {
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
@@ -99,6 +103,10 @@ public class FastBitmapDrawable extends Drawable {
private State mState = State.NORMAL;
private boolean mIsDisabled;
+ private BadgeInfo mBadgeInfo;
+ private BadgeRenderer mBadgeRenderer;
+ private IconPalette mIconPalette;
+
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
private int mDesaturation = 0;
@@ -114,9 +122,21 @@ public class FastBitmapDrawable extends Drawable {
setBounds(0, 0, b.getWidth(), b.getHeight());
}
+ public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
+ mBadgeInfo = badgeInfo;
+ mBadgeRenderer = badgeRenderer;
+ if (mIconPalette == null) {
+ mIconPalette = IconPalette.fromDominantColor(Utilities
+ .findDominantColorByHue(mBitmap, 20));
+ }
+ invalidateSelf();
+ }
+
@Override
public void draw(Canvas canvas) {
drawInternal(canvas);
+ // Draw the icon badge in the top right corner.
+ drawBadgeIfNecessary(canvas);
}
public void drawWithBrightness(Canvas canvas, float brightness) {
@@ -130,6 +150,16 @@ public class FastBitmapDrawable extends Drawable {
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
+ protected void drawBadgeIfNecessary(Canvas canvas) {
+ if (hasBadge()) {
+ mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds());
+ }
+ }
+
+ private boolean hasBadge() {
+ return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != null;
+ }
+
@Override
public void setColorFilter(ColorFilter cf) {
// No op
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 89ffd315d..8ad0144c3 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -306,7 +306,7 @@ public final class Utilities {
* @param bitmap The bitmap to scan
* @param samples The approximate max number of samples to use.
*/
- static int findDominantColorByHue(Bitmap bitmap, int samples) {
+ public static int findDominantColorByHue(Bitmap bitmap, int samples) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int sampleStride = (int) Math.sqrt((height * width) / samples);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index cf6b02505..1ba6520be 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3972,7 +3972,7 @@ public class Workspace extends PagedView
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof ShortcutInfo && v instanceof BubbleTextView
&& updates.contains(info)) {
- ((BubbleTextView) v).applyState(false);
+ ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
} else if (v instanceof PendingAppWidgetHostView
&& info instanceof LauncherAppWidgetInfo
&& updates.contains(info)) {
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
new file mode 100644
index 000000000..0a9f87c6e
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -0,0 +1,33 @@
+/*
+ * 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.launcher3.badge;
+
+/**
+ * Contains data to be used in an icon badge.
+ */
+public class BadgeInfo {
+
+ private int mNotificationCount;
+
+ public void setNotificationCount(int count) {
+ mNotificationCount = count;
+ }
+
+ public String getNotificationCount() {
+ return mNotificationCount == 0 ? null : String.valueOf(mNotificationCount);
+ }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
new file mode 100644
index 000000000..238b9188f
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.launcher3.badge;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.launcher3.graphics.IconPalette;
+
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ * @see BadgeInfo for the data to draw
+ */
+public class BadgeRenderer {
+
+ public int size;
+ public int textSize;
+
+ private final RectF mBackgroundRect = new RectF();
+ private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final int mTextHeight;
+
+ public BadgeRenderer(int size, int textSize) {
+ this.size = size;
+ this.textSize = textSize;
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setTextSize(textSize);
+ // Measure the text height.
+ Rect temp = new Rect();
+ mTextPaint.getTextBounds("0", 0, 1, temp);
+ mTextHeight = temp.height();
+ }
+
+ /**
+ * Draw a circle in the top right corner of the given bounds, and draw
+ * {@link BadgeInfo#getNotificationCount()} on top of the circle.
+ * @param palette The colors (based on the icon) to use for the badge.
+ * @param badgeInfo Contains data to draw on the badge.
+ * @param iconBounds The bounds of the icon being badged.
+ */
+ public void draw(Canvas canvas, IconPalette palette, BadgeInfo badgeInfo, Rect iconBounds) {
+ mBackgroundPaint.setColor(palette.backgroundColor);
+ mTextPaint.setColor(palette.textColor);
+ mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right,
+ iconBounds.top + size);
+ canvas.drawOval(mBackgroundRect, mBackgroundPaint);
+ String notificationCount = badgeInfo.getNotificationCount();
+ canvas.drawText(notificationCount,
+ mBackgroundRect.centerX(),
+ mBackgroundRect.centerY() + mTextHeight / 2,
+ mTextPaint);
+ }
+}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
new file mode 100644
index 000000000..dcc5fcb14
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -0,0 +1,63 @@
+/*
+ * 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.launcher3.graphics;
+
+import android.graphics.Color;
+import android.support.v4.graphics.ColorUtils;
+
+/**
+ * Contains colors based on the dominant color of an icon.
+ */
+public class IconPalette {
+
+ public int backgroundColor;
+ public int textColor;
+
+ public static IconPalette fromDominantColor(int dominantColor) {
+ IconPalette palette = new IconPalette();
+ palette.backgroundColor = getMutedColor(dominantColor);
+ palette.textColor = getTextColorForBackground(palette.backgroundColor);
+ return palette;
+ }
+
+ private static int getMutedColor(int color) {
+ int alpha = (int) (255 * 0.2f);
+ return ColorUtils.compositeColors(ColorUtils.setAlphaComponent(color, alpha), Color.WHITE);
+ }
+
+ private static int getTextColorForBackground(int backgroundColor) {
+ return getLighterOrDarkerVersionOfColor(backgroundColor, 3f);
+ }
+
+ private static int getLowContrastColor(int color) {
+ return getLighterOrDarkerVersionOfColor(color, 1.5f);
+ }
+
+ private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
+ int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
+ int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
+ int translucentWhiteOrBlack;
+ if (whiteMinAlpha >= 0) {
+ translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
+ } else if (blackMinAlpha >= 0) {
+ translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
+ } else {
+ translucentWhiteOrBlack = Color.WHITE;
+ }
+ return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
+ }
+}