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>
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;
applyIconAndLabel(info.iconBitmap, info);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
- applyState(promiseStateChanged);
+ applyPromiseState(promiseStateChanged);
}
}
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();
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) {
}
}
+ 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) &&
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;
// 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) {
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;
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;
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;
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) {
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
* @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);
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)) {
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}