feat: Add option to clear home screen in settings (#6125)

Signed-off-by: abhixv <abhi.sharma1@hotmail.com>
This commit is contained in:
Abhishek Sharma
2025-12-20 03:26:30 +05:30
committed by Pun Butrach
parent 9898749619
commit 5f3a03f4fb
1577 changed files with 112563 additions and 80248 deletions
@@ -16,30 +16,24 @@
package com.android.launcher3.taskbar.bubbles;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
import com.android.launcher3.icons.DotRenderer;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleInfo;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.animation.Interpolators;
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
import app.lawnchair.preferences2.PreferenceManager2;
import app.lawnchair.theme.color.ColorOption;
@@ -49,18 +43,34 @@ import java.util.EnumSet;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
/**
* View that displays a bubble icon, along with an app badge on either the left or
* View that displays a bubble icon, along with an app badge on either the left
* or
* right side of the view.
*/
public class BubbleView extends ConstraintLayout {
public static final int DEFAULT_PATH_SIZE = 100;
/** Duration for animating the scale of the dot and badge. */
private static final int SCALE_ANIMATION_DURATION_MS = 200;
/**
* Flags that suppress the visibility of the 'new' dot or the app badge, for one
* reason or
* another. If any of these flags are set, the dot will not be shown.
* If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
*/
enum SuppressionFlag {
// TODO: (b/277815200) implement flyout
// Suppressed because the flyout is visible - it will morph into the dot via
// animation.
FLYOUT_VISIBLE,
// Suppressed because this bubble is behind others in the collapsed stack.
BEHIND_STACK,
}
private final EnumSet<SuppressionFlag> mSuppressionFlags = EnumSet.noneOf(SuppressionFlag.class);
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
private int mBubbleSize;
private final int mBubbleSize;
private float mDragTranslationX;
private float mOffsetX;
@@ -76,22 +86,12 @@ public class BubbleView extends ConstraintLayout {
private float mAnimatingToDotScale;
// The current scale value of the dot
private float mDotScale;
private boolean mDotSuppressedForBubbleUpdate = false;
// TODO: (b/273310265) handle RTL
// Whether the bubbles are positioned on the left or right side of the screen
private boolean mOnLeft = false;
private BubbleBarItem mBubble;
private boolean mIsOverflow;
private Bitmap mIcon;
@Nullable
private Controller mController;
@Nullable
private BubbleBarBubbleIconsFactory mIconFactory = null;
PreferenceManager2 preferenceManager2;
@@ -114,6 +114,8 @@ public class BubbleView extends ConstraintLayout {
setLayoutDirection(LAYOUT_DIRECTION_LTR);
LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
@@ -123,26 +125,24 @@ public class BubbleView extends ConstraintLayout {
setFocusable(true);
setClickable(true);
// We manage the shadow ourselves when creating the bitmap
setOutlineAmbientShadowColor(Color.TRANSPARENT);
setOutlineSpotShadowColor(Color.TRANSPARENT);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
BubbleView.this.getOutline(outline);
}
});
}
private void updateBubbleSizeAndDotRender() {
int updatedBubbleSize = Math.min(getWidth(), getHeight());
if (updatedBubbleSize == mBubbleSize) return;
mBubbleSize = updatedBubbleSize;
mIconFactory = new BubbleBarBubbleIconsFactory(mContext, mBubbleSize);
updateBubbleIcon();
if (mBubble == null || mBubble instanceof BubbleBarOverflow) return;
Path dotPath = ((BubbleBarBubble) mBubble).getDotPath();
mDotRenderer = new DotRenderer(mBubbleSize, dotPath, DEFAULT_PATH_SIZE);
private void getOutline(Outline outline) {
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
final int inset = (mBubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
/**
* Set translation-x while this bubble is being dragged.
* Translation applied to the view is a sum of {@code translationX} and offset defined by
* Translation applied to the view is a sum of {@code translationX} and offset
* defined by
* {@link #setOffsetX(float)}.
*/
public void setDragTranslationX(float translationX) {
@@ -159,9 +159,11 @@ public class BubbleView extends ConstraintLayout {
/**
* Set offset on x-axis while dragging.
* Used to counter parent translation in order to keep the dragged view at the current position
* Used to counter parent translation in order to keep the dragged view at the
* current position
* on screen.
* Translation applied to the view is a sum of {@code offsetX} and translation defined by
* Translation applied to the view is a sum of {@code offsetX} and translation
* defined by
* {@link #setDragTranslationX(float)}
*/
public void setOffsetX(float offsetX) {
@@ -173,12 +175,6 @@ public class BubbleView extends ConstraintLayout {
setTranslationX(mDragTranslationX + mOffsetX);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateBubbleSizeAndDotRender();
}
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -189,78 +185,19 @@ public class BubbleView extends ConstraintLayout {
getDrawingRect(mTempBounds);
mDrawParams.dotColor = mDotColor;
mDrawParams.appColor = mDotColor;
mDrawParams.iconBounds = mTempBounds;
mDrawParams.leftAlign = mOnLeft;
mDrawParams.scale = mDotScale;
mDotRenderer.draw(canvas, mDrawParams);
}
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
if (mBubble instanceof BubbleBarBubble) {
info.addAction(AccessibilityNodeInfo.ACTION_DISMISS);
}
if (mController != null) {
if (mController.getBubbleBarLocation().isOnLeft(isLayoutRtl())) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(R.id.action_move_right,
getResources().getString(R.string.bubble_bar_action_move_right)));
} else {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(R.id.action_move_left,
getResources().getString(R.string.bubble_bar_action_move_left)));
}
}
}
@Override
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) {
if (mController != null) {
mController.collapse();
}
return true;
}
if (action == AccessibilityNodeInfo.ACTION_DISMISS) {
if (mController != null) {
mController.dismiss(this);
}
return true;
}
if (action == R.id.action_move_left) {
if (mController != null) {
mController.updateBubbleBarLocation(BubbleBarLocation.LEFT,
BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE);
}
}
if (action == R.id.action_move_right) {
if (mController != null) {
mController.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE);
}
}
return false;
}
void setController(@Nullable Controller controller) {
mController = controller;
mDotRenderer.draw(canvas, mDrawParams, -1);
}
/** Sets the bubble being rendered in this view. */
public void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mIcon = bubble.getIcon();
updateBubbleIcon();
if (bubble.getInfo().showAppBadge()) {
mAppIcon.setImageBitmap(bubble.getBadge());
} else {
mAppIcon.setVisibility(GONE);
}
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
mDotColor = bubble.getDotColor();
ColorOption dotColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
int dotColor = dotColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
@@ -281,53 +218,32 @@ public class BubbleView extends ConstraintLayout {
setContentDescription(contentDesc);
}
private void updateBubbleIcon() {
Bitmap icon = null;
if (mIcon != null) {
icon = mIcon;
if (mIconFactory != null) {
BitmapDrawable iconDrawable = new BitmapDrawable(getResources(), icon);
icon = mIconFactory.createShadowedIconBitmap(iconDrawable, /* scale = */ 1f);
}
}
mBubbleIcon.setImageBitmap(icon);
}
/**
* Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
* but does not represent app content, instead it shows recent bubbles that couldn't fit into
* the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
* Sets that this bubble represents the overflow. The overflow appears in the
* list of bubbles
* but does not represent app content, instead it shows recent bubbles that
* couldn't fit into
* the list of bubbles. It doesn't show an app icon because it is part of system
* UI / doesn't
* come from an app.
*/
public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
mBubble = overflow;
mIsOverflow = true;
mIcon = bitmap;
updateBubbleIcon();
mBubbleIcon.setImageBitmap(bitmap);
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
}
/** Whether this view represents the overflow button. */
public boolean isOverflow() {
return mIsOverflow;
}
/** Returns the bubble being rendered in this view. */
@Nullable
public BubbleBarItem getBubble() {
return mBubble;
}
/** Updates the dot visibility if it's not suppressed based on whether it has unseen content. */
public void updateDotVisibility(boolean animate) {
if (mDotSuppressedForBubbleUpdate) {
// if the dot is suppressed for an update, there's nothing to do
return;
}
final float targetScale = hasUnseenContent() ? 1f : 0f;
void updateDotVisibility(boolean animate) {
final float targetScale = shouldDrawDot() ? 1f : 0f;
if (animate) {
animateDotScale(targetScale);
animateDotScale();
} else {
mDotScale = targetScale;
mAnimatingToDotScale = targetScale;
@@ -335,132 +251,77 @@ public class BubbleView extends ConstraintLayout {
}
}
void setBadgeScale(float fraction) {
if (hasBadge()) {
mAppIcon.setScaleX(fraction);
mAppIcon.setScaleY(fraction);
}
}
void showBadge() {
animateBadgeScale(1);
}
void hideBadge() {
animateBadgeScale(0);
}
private boolean hasBadge() {
return mAppIcon.getVisibility() == VISIBLE;
}
private void animateBadgeScale(float scale) {
if (!hasBadge()) {
void updateBadgeVisibility() {
if (mBubble instanceof BubbleBarOverflow) {
// The overflow bubble does not have a badge, so just bail.
return;
}
mAppIcon.clearAnimation();
mAppIcon.animate()
.setDuration(SCALE_ANIMATION_DURATION_MS)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.scaleX(scale)
.scaleY(scale)
.start();
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
Bitmap appBadgeBitmap = bubble.getBadge();
int translationX = mOnLeft
? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
: 0;
mAppIcon.setTranslationX(translationX);
mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
}
/** Suppresses drawing the dot due to an update for this bubble. */
public void suppressDotForBubbleUpdate() {
mDotSuppressedForBubbleUpdate = true;
setDotScale(0);
/** Sets whether this bubble is in the stack & not the first bubble. **/
void setBehindStack(boolean behindStack, boolean animate) {
if (behindStack) {
mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
} else {
mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
}
updateDotVisibility(animate);
updateBadgeVisibility();
}
/**
* Unsuppresses the dot after the bubble update finished animating.
*
* @param animate whether or not to animate the dot back in
*/
public void unsuppressDotForBubbleUpdate(boolean animate) {
mDotSuppressedForBubbleUpdate = false;
showDotIfNeeded(animate);
/** Whether this bubble is in the stack & not the first bubble. **/
boolean isBehindStack() {
return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
}
boolean hasUnseenContent() {
return mBubble != null
&& mBubble instanceof BubbleBarBubble
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
}
/**
* Used to determine if we can skip drawing frames.
*
* <p>Generally we should draw the dot when it is requested to be shown and there is unseen
* content. But when the dot is removed, we still want to draw frames so that it can be scaled
* out.
*/
/** Whether the dot indicating unseen content in a bubble should be shown. */
private boolean shouldDrawDot() {
// if there's no dot there's nothing to draw, unless the dot was removed and we're in the
// middle of removing it
return hasUnseenContent() || mDotIsAnimating;
boolean bubbleHasUnseenContent = mBubble != null
&& mBubble instanceof BubbleBarBubble
&& mSuppressionFlags.isEmpty()
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
// Always render the dot if it's animating, since it could be animating out.
// Otherwise, show
// it if the bubble wants to show it, and we aren't suppressing it.
return bubbleHasUnseenContent || mDotIsAnimating;
}
/** Updates the dot scale to the specified fraction from 0 to 1. */
/** How big the dot should be, fraction from 0 to 1. */
private void setDotScale(float fraction) {
if (!shouldDrawDot()) {
return;
}
mDotScale = fraction;
invalidate();
}
void showDotIfNeeded(float fraction) {
if (!hasUnseenContent()) {
return;
}
setDotScale(fraction);
}
void showDotIfNeeded(boolean animate) {
// only show the dot if we have unseen content and it's not suppressed
if (!hasUnseenContent() || mDotSuppressedForBubbleUpdate) {
return;
}
if (animate) {
animateDotScale(1f);
} else {
setDotScale(1f);
}
}
void hideDot() {
animateDotScale(0f);
}
/** Marks this bubble such that it no longer has unseen content, and hides the dot. */
void markSeen() {
if (mBubble instanceof BubbleBarBubble bubble) {
BubbleInfo info = bubble.getInfo();
info.setFlags(
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
hideDot();
}
}
/** Animates the dot to the given scale. */
private void animateDotScale(float toScale) {
boolean isDotScaleChanging = Float.compare(mDotScale, toScale) != 0;
// Don't restart the animation if we're already animating to the given value or if the dot
// scale is not changing
if ((mDotIsAnimating && mAnimatingToDotScale == toScale) || !isDotScaleChanging) {
return;
}
/**
* Animates the dot to the given scale.
*/
private void animateDotScale() {
float toScale = shouldDrawDot() ? 1f : 0f;
mDotIsAnimating = true;
// Don't restart the animation if we're already animating to the given value.
if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
mDotIsAnimating = false;
return;
}
mAnimatingToDotScale = toScale;
final boolean showDot = toScale > 0f;
// Do NOT wait until after animation ends to setShowDot
// to avoid overriding more recent showDot states.
clearAnimation();
animate()
.setDuration(SCALE_ANIMATION_DURATION_MS)
.setDuration(200)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setUpdateListener((valueAnimator) -> {
float fraction = valueAnimator.getAnimatedFraction();
@@ -472,42 +333,9 @@ public class BubbleView extends ConstraintLayout {
}).start();
}
/**
* Returns the distance from the top left corner of this bubble view to the center of its dot.
*/
public PointF getDotCenter() {
float[] dotPosition =
mOnLeft ? mDotRenderer.getLeftDotPosition() : mDotRenderer.getRightDotPosition();
getDrawingRect(mTempBounds);
float dotCenterX = mTempBounds.width() * dotPosition[0];
float dotCenterY = mTempBounds.height() * dotPosition[1];
return new PointF(dotCenterX, dotCenterY);
}
/** Returns the dot color. */
public int getDotColor() {
return mDotColor;
}
@Override
public String toString() {
String toString = mBubble != null ? mBubble.getKey() : "null";
return "BubbleView{" + toString + "}";
}
/** Interface for BubbleView to communicate with its controller */
public interface Controller {
/** Get current bubble bar {@link BubbleBarLocation} */
BubbleBarLocation getBubbleBarLocation();
/** This bubble should be dismissed */
void dismiss(BubbleView bubble);
/** Collapse the bubble bar */
void collapse();
/** Request bubble bar location to be updated to the given location */
void updateBubbleBarLocation(BubbleBarLocation location,
@BubbleBarLocation.UpdateSource int source);
}
}