Normalized icon bitmap
Extend Launcher's BaseIconFactory to create bubble icon instead of manually creating AdaptiveIconDrawable. Launcher normalizes circles bigger and squares smaller to account for the visual difference in size. This results in padding between bubble view and the actual icon. This change - increases individual_bubble_size to account for this padding - preserves the previous value in bubble_icon_bitmap_size - includes various space / animation adjustments to preserve existing UX - removes manual shadow drawing from bubble stack view, since it's already provided by Launcher Bug: 129158983 Test: manual (create bubbles, size and spacing still look good in collapsed and expanded states) Test: manual (create bubble, flyout open / close animations look good) Test: atest SystemUITests Change-Id: Icf63a2be57daff21ec64d2e9ac0eb0cd96af0399
This commit is contained in:
@@ -113,6 +113,7 @@ android_library {
|
||||
"androidx.lifecycle_lifecycle-extensions",
|
||||
"androidx.dynamicanimation_dynamicanimation",
|
||||
"androidx-constraintlayout_constraintlayout",
|
||||
"iconloader_base",
|
||||
"SystemUI-tags",
|
||||
"SystemUI-proto",
|
||||
"metrics-helper-lib",
|
||||
|
||||
@@ -1110,7 +1110,9 @@
|
||||
<!-- Padding between status bar and bubbles when displayed in expanded state -->
|
||||
<dimen name="bubble_padding_top">16dp</dimen>
|
||||
<!-- Size of individual bubbles. -->
|
||||
<dimen name="individual_bubble_size">52dp</dimen>
|
||||
<dimen name="individual_bubble_size">60dp</dimen>
|
||||
<!-- Size of bubble icon bitmap. -->
|
||||
<dimen name="bubble_icon_bitmap_size">52dp</dimen>
|
||||
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
|
||||
<dimen name="bubble_dismiss_encircle_size">56dp</dimen>
|
||||
<!-- How much to inset the icon in the circle -->
|
||||
@@ -1142,9 +1144,9 @@
|
||||
<!-- Offset between bubbles in their stacked position. -->
|
||||
<dimen name="bubble_stack_offset">5dp</dimen>
|
||||
<!-- How far offscreen the bubble stack rests. -->
|
||||
<dimen name="bubble_stack_offscreen">5dp</dimen>
|
||||
<dimen name="bubble_stack_offscreen">9dp</dimen>
|
||||
<!-- How far down the screen the stack starts. -->
|
||||
<dimen name="bubble_stack_starting_offset_y">100dp</dimen>
|
||||
<dimen name="bubble_stack_starting_offset_y">96dp</dimen>
|
||||
<!-- Size of image buttons in the bubble header -->
|
||||
<dimen name="bubble_header_icon_size">48dp</dimen>
|
||||
<!-- Space between the pointer triangle and the bubble expanded view -->
|
||||
|
||||
@@ -55,9 +55,9 @@ public class BadgeRenderer {
|
||||
|
||||
/** Space between the center of the dot and the top or left of the bubble stack. */
|
||||
static float getDotCenterOffset(Context context) {
|
||||
final int iconSizePx =
|
||||
context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
|
||||
return SIZE_PERCENTAGE * iconSizePx;
|
||||
final int iconBitmapSize =
|
||||
context.getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
|
||||
return SIZE_PERCENTAGE * iconBitmapSize;
|
||||
}
|
||||
|
||||
static float getDotRadius(float dotCenterOffset) {
|
||||
|
||||
@@ -32,7 +32,8 @@ import com.android.systemui.R;
|
||||
public class BadgedImageView extends ImageView {
|
||||
|
||||
private BadgeRenderer mDotRenderer;
|
||||
private int mIconSize;
|
||||
private int mIconBitmapSize;
|
||||
|
||||
private Rect mTempBounds = new Rect();
|
||||
private Point mTempPoint = new Point();
|
||||
|
||||
@@ -56,7 +57,7 @@ public class BadgedImageView extends ImageView {
|
||||
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
|
||||
mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
|
||||
mDotRenderer = new BadgeRenderer(getContext());
|
||||
|
||||
TypedArray ta = context.obtainStyledAttributes(
|
||||
@@ -69,7 +70,7 @@ public class BadgedImageView extends ImageView {
|
||||
super.onDraw(canvas);
|
||||
if (mShowUpdateDot) {
|
||||
getDrawingRect(mTempBounds);
|
||||
mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
|
||||
mTempPoint.set((getWidth() - mIconBitmapSize) / 2, getPaddingTop());
|
||||
mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
|
||||
mOnLeft);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ public class BubbleFlyoutView extends FrameLayout {
|
||||
private final int mFlyoutSpaceFromBubble;
|
||||
private final int mPointerSize;
|
||||
private final int mBubbleSize;
|
||||
private final int mBubbleIconBitmapSize;
|
||||
private final int mFlyoutElevation;
|
||||
private final int mBubbleElevation;
|
||||
private final int mFloatingBackgroundColor;
|
||||
@@ -143,7 +144,9 @@ public class BubbleFlyoutView extends FrameLayout {
|
||||
mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
|
||||
mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
|
||||
mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
|
||||
|
||||
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
|
||||
mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
|
||||
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||
mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
|
||||
mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
|
||||
@@ -216,7 +219,8 @@ public class BubbleFlyoutView extends FrameLayout {
|
||||
post(() -> {
|
||||
// Multi line flyouts get top-aligned to the bubble.
|
||||
if (mFlyoutText.getLineCount() > 1) {
|
||||
setTranslationY(stackPos.y);
|
||||
float bubbleIconTopPadding = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
|
||||
setTranslationY(stackPos.y + bubbleIconTopPadding);
|
||||
} else {
|
||||
// Single line flyouts are vertically centered with respect to the bubble.
|
||||
setTranslationY(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.systemui.bubbles;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.icons.BaseIconFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating normalized bubble icons.
|
||||
* We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
|
||||
* so there is no need to manage a pool across multiple threads.
|
||||
*/
|
||||
public class BubbleIconFactory extends BaseIconFactory {
|
||||
protected BubbleIconFactory(Context context, int iconBitmapSize) {
|
||||
super(context, context.getResources().getConfiguration().densityDpi, iconBitmapSize);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
@@ -49,7 +48,6 @@ import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
@@ -183,7 +181,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
private int mStatusBarHeight;
|
||||
private int mPipDismissHeight;
|
||||
private int mImeOffset;
|
||||
|
||||
private BubbleIconFactory mBubbleIconFactory;
|
||||
private Bubble mExpandedBubble;
|
||||
private boolean mIsExpanded;
|
||||
private boolean mImeVisible;
|
||||
@@ -284,7 +282,8 @@ public class BubbleStackView extends FrameLayout {
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
|
||||
@NonNull
|
||||
private final SurfaceSynchronizer mSurfaceSynchronizer;
|
||||
|
||||
private BubbleDismissView mDismissContainer;
|
||||
private Runnable mAfterMagnet;
|
||||
@@ -337,6 +336,9 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleContainer.setClipChildren(false);
|
||||
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
|
||||
int iconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
|
||||
mBubbleIconFactory = new BubbleIconFactory(context, iconBitmapSize);
|
||||
|
||||
mExpandedViewContainer = new FrameLayout(context);
|
||||
mExpandedViewContainer.setElevation(elevation);
|
||||
mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
|
||||
@@ -455,7 +457,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
* Handle theme changes.
|
||||
*/
|
||||
public void onThemeChanged() {
|
||||
for (Bubble b: mBubbleData.getBubbles()) {
|
||||
for (Bubble b : mBubbleData.getBubbles()) {
|
||||
b.iconView.updateViews();
|
||||
b.expandedView.applyThemeAttrs();
|
||||
}
|
||||
@@ -618,6 +620,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Updates the visibility of the 'dot' indicating an update on the bubble.
|
||||
*
|
||||
* @param key the {@link NotificationEntry#key} associated with the bubble.
|
||||
*/
|
||||
public void updateDotVisibility(String key) {
|
||||
@@ -690,6 +693,9 @@ public class BubbleStackView extends FrameLayout {
|
||||
Log.d(TAG, "addBubble: " + bubble);
|
||||
}
|
||||
bubble.inflate(mInflater, this);
|
||||
bubble.iconView.setBubbleIconFactory(mBubbleIconFactory);
|
||||
bubble.iconView.updateViews();
|
||||
|
||||
mBubbleContainer.addView(bubble.iconView, 0,
|
||||
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||
ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
|
||||
@@ -796,6 +802,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Dismiss the stack of bubbles.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -1518,24 +1525,12 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
/** Sets the appropriate Z-order and dot position for each bubble in the stack. */
|
||||
private void updateBubbleShadowsAndDotPosition(boolean animate) {
|
||||
int bubbsCount = mBubbleContainer.getChildCount();
|
||||
for (int i = 0; i < bubbsCount; i++) {
|
||||
int bubbleCount = mBubbleContainer.getChildCount();
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
|
||||
bv.updateDotVisibility(true /* animate */);
|
||||
bv.setZ((BubbleController.MAX_BUBBLES
|
||||
* getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
|
||||
|
||||
// Draw the shadow around the circle inscribed within the bubble's bounds. This
|
||||
// (intentionally) does not draw a shadow behind the update dot, which should be drawing
|
||||
// its own shadow since it's on a different (higher) plane.
|
||||
bv.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setOval(0, 0, mBubbleSize, mBubbleSize);
|
||||
}
|
||||
});
|
||||
bv.setClipToOutline(false);
|
||||
|
||||
// If the dot is on the left, and so is the stack, we need to change the dot position.
|
||||
if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
|
||||
bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.internal.graphics.ColorUtils;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
@@ -41,7 +42,7 @@ public class BubbleView extends FrameLayout {
|
||||
|
||||
private static final int DARK_ICON_ALPHA = 180;
|
||||
private static final double ICON_MIN_CONTRAST = 4.1;
|
||||
private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
|
||||
private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
|
||||
// Same value as Launcher3 badge code
|
||||
private static final float WHITE_SCRIM_ALPHA = 0.54f;
|
||||
private Context mContext;
|
||||
@@ -50,6 +51,9 @@ public class BubbleView extends FrameLayout {
|
||||
private int mBadgeColor;
|
||||
private int mIconInset;
|
||||
|
||||
// mBubbleIconFactory cannot be static because it depends on Context.
|
||||
private BubbleIconFactory mBubbleIconFactory;
|
||||
|
||||
private boolean mSuppressDot = false;
|
||||
|
||||
private NotificationEntry mEntry;
|
||||
@@ -93,7 +97,6 @@ public class BubbleView extends FrameLayout {
|
||||
*/
|
||||
public void setNotif(NotificationEntry entry) {
|
||||
mEntry = entry;
|
||||
updateViews();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,6 +123,13 @@ public class BubbleView extends FrameLayout {
|
||||
updateViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param factory Factory for creating normalized bubble icons.
|
||||
*/
|
||||
public void setBubbleIconFactory(BubbleIconFactory factory) {
|
||||
mBubbleIconFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ExpandableNotificationRow} view to display notification content when the
|
||||
* bubble is expanded.
|
||||
@@ -203,7 +213,7 @@ public class BubbleView extends FrameLayout {
|
||||
}
|
||||
|
||||
void updateViews() {
|
||||
if (mEntry == null) {
|
||||
if (mEntry == null || mBubbleIconFactory == null) {
|
||||
return;
|
||||
}
|
||||
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
|
||||
@@ -219,10 +229,13 @@ public class BubbleView extends FrameLayout {
|
||||
}
|
||||
Drawable iconDrawable = ic.loadDrawable(mContext);
|
||||
if (needsTint) {
|
||||
mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
|
||||
} else {
|
||||
mBadgedImageView.setImageDrawable(iconDrawable);
|
||||
iconDrawable = buildIconWithTint(iconDrawable, n.color);
|
||||
}
|
||||
BitmapInfo bitmapInfo = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
|
||||
null /* user */,
|
||||
true /* shrinkNonAdaptiveIcons */);
|
||||
mBadgedImageView.setImageBitmap(bitmapInfo.icon);
|
||||
|
||||
int badgeColor = determineDominateColor(iconDrawable, n.color);
|
||||
mBadgeColor = badgeColor;
|
||||
mBadgedImageView.setDotColor(badgeColor);
|
||||
|
||||
@@ -86,10 +86,12 @@ public class ExpandedAnimationController
|
||||
private boolean mSpringingBubbleToTouch = false;
|
||||
|
||||
private int mExpandedViewPadding;
|
||||
private float mLauncherGridDiff;
|
||||
|
||||
public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
|
||||
mDisplaySize = displaySize;
|
||||
mExpandedViewPadding = expandedViewPadding;
|
||||
mLauncherGridDiff = 30f;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,8 +440,7 @@ public class ExpandedAnimationController
|
||||
* [launcherGridDiff] --- arbitrary value until launcher exports widths
|
||||
* Launcher's app icon grid edge that we must match
|
||||
*/
|
||||
final float launcherGridDiff = mBubbleSizePx / 2f;
|
||||
final float rowMargins = (mExpandedViewPadding + launcherGridDiff) * 2;
|
||||
final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
|
||||
final float maxRowWidth = mDisplaySize.x - rowMargins;
|
||||
|
||||
final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
|
||||
|
||||
@@ -140,7 +140,7 @@ public class StackAnimationController extends
|
||||
/** Horizontal offset of bubbles in the stack. */
|
||||
private float mStackOffset;
|
||||
/** Diameter of the bubbles themselves. */
|
||||
private int mIndividualBubbleSize;
|
||||
private int mBubbleIconBitmapSize;
|
||||
/**
|
||||
* The amount of space to add between the bubbles and certain UI elements, such as the top of
|
||||
* the screen or the IME. This does not apply to the left/right sides of the screen since the
|
||||
@@ -185,7 +185,7 @@ public class StackAnimationController extends
|
||||
return false;
|
||||
}
|
||||
|
||||
float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
|
||||
float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
|
||||
float screenCenter = mLayout.getWidth() / 2;
|
||||
return stackCenter < screenCenter;
|
||||
}
|
||||
@@ -218,7 +218,7 @@ public class StackAnimationController extends
|
||||
* @return The X value that the stack will end up at after the fling/spring.
|
||||
*/
|
||||
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
|
||||
final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
|
||||
final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
|
||||
|
||||
final boolean stackShouldFlingLeft = stackOnLeftSide
|
||||
? velX < ESCAPE_VELOCITY
|
||||
@@ -427,7 +427,7 @@ public class StackAnimationController extends
|
||||
: 0);
|
||||
allowableRegion.right =
|
||||
mLayout.getWidth()
|
||||
- mIndividualBubbleSize
|
||||
- mBubbleIconBitmapSize
|
||||
+ mBubbleOffscreen
|
||||
- Math.max(
|
||||
insets.getSystemWindowInsetRight(),
|
||||
@@ -444,7 +444,7 @@ public class StackAnimationController extends
|
||||
: 0);
|
||||
allowableRegion.bottom =
|
||||
mLayout.getHeight()
|
||||
- mIndividualBubbleSize
|
||||
- mBubbleIconBitmapSize
|
||||
- mBubblePaddingTop
|
||||
- (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
|
||||
- Math.max(
|
||||
@@ -517,7 +517,7 @@ public class StackAnimationController extends
|
||||
mFirstBubbleSpringingToTouch = false;
|
||||
|
||||
animationForChildAtIndex(0)
|
||||
.translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
|
||||
.translationX(mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f)
|
||||
.translationY(destY, after)
|
||||
.withPositionStartVelocities(velX, velY)
|
||||
.withStiffness(SpringForce.STIFFNESS_MEDIUM)
|
||||
@@ -657,7 +657,7 @@ public class StackAnimationController extends
|
||||
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
|
||||
Resources res = layout.getResources();
|
||||
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
||||
mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
|
||||
mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
|
||||
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
||||
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
|
||||
mStackStartingVerticalOffset =
|
||||
|
||||
@@ -46,6 +46,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
|
||||
private int mDisplayWidth = 500;
|
||||
private int mDisplayHeight = 1000;
|
||||
private int mExpandedViewPadding = 10;
|
||||
private float mLauncherGridDiff = 30f;
|
||||
|
||||
@Spy
|
||||
private ExpandedAnimationController mExpandedController =
|
||||
@@ -281,8 +282,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
|
||||
* @return Space between bubbles in row above expanded view.
|
||||
*/
|
||||
private float getSpaceBetweenBubbles() {
|
||||
final float launcherGridDiff = mBubbleSize / 2f;
|
||||
final float rowMargins = (mExpandedViewPadding + launcherGridDiff) * 2;
|
||||
final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
|
||||
final float maxRowWidth = mDisplayWidth - rowMargins;
|
||||
|
||||
final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
|
||||
|
||||
Reference in New Issue
Block a user