Merge changes I4794ed89,I0c273f4d,Ibd8609a8 into sc-dev
* changes: Flexible maximum number of bubbles to show Add shadows back to bubble avatar Update sizes for the bubble avatar
This commit is contained in:
@@ -25,8 +25,8 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/bubble_view"
|
android:id="@+id/bubble_view"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_width="@dimen/individual_bubble_size"
|
android:layout_width="@dimen/bubble_size"
|
||||||
android:layout_height="@dimen/individual_bubble_size"/>
|
android:layout_height="@dimen/bubble_size"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/bubble_view_name"
|
android:id="@+id/bubble_view_name"
|
||||||
|
|||||||
@@ -101,12 +101,12 @@
|
|||||||
<dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
|
<dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
|
||||||
<!-- Padding between status bar and bubbles when displayed in expanded state -->
|
<!-- Padding between status bar and bubbles when displayed in expanded state -->
|
||||||
<dimen name="bubble_padding_top">16dp</dimen>
|
<dimen name="bubble_padding_top">16dp</dimen>
|
||||||
<!-- Max amount of space between bubbles when expanded. -->
|
<!-- Space between bubbles when expanded. -->
|
||||||
<dimen name="bubble_max_spacing">8dp</dimen>
|
<dimen name="bubble_spacing">3dp</dimen>
|
||||||
<!-- Size of individual bubbles. -->
|
<!-- Size of the bubble. -->
|
||||||
<dimen name="individual_bubble_size">60dp</dimen>
|
<dimen name="bubble_size">60dp</dimen>
|
||||||
<!-- Size of bubble bitmap. -->
|
<!-- Size of the badge shown on the bubble. -->
|
||||||
<dimen name="bubble_bitmap_size">52dp</dimen>
|
<dimen name="bubble_badge_size">24dp</dimen>
|
||||||
<!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
|
<!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
|
||||||
<dimen name="bubble_touch_padding">12dp</dimen>
|
<dimen name="bubble_touch_padding">12dp</dimen>
|
||||||
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
|
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
|
||||||
@@ -120,6 +120,8 @@
|
|||||||
<dimen name="bubble_expanded_default_height">180dp</dimen>
|
<dimen name="bubble_expanded_default_height">180dp</dimen>
|
||||||
<!-- On large screens the width of the expanded view is restricted to this size. -->
|
<!-- On large screens the width of the expanded view is restricted to this size. -->
|
||||||
<dimen name="bubble_expanded_view_tablet_width">412dp</dimen>
|
<dimen name="bubble_expanded_view_tablet_width">412dp</dimen>
|
||||||
|
<!-- Inset to apply to the icon in the overflow button. -->
|
||||||
|
<dimen name="bubble_overflow_icon_inset">30dp</dimen>
|
||||||
<!-- Default (and minimum) height of bubble overflow -->
|
<!-- Default (and minimum) height of bubble overflow -->
|
||||||
<dimen name="bubble_overflow_height">480dp</dimen>
|
<dimen name="bubble_overflow_height">480dp</dimen>
|
||||||
<!-- Bubble overflow padding when there are no bubbles -->
|
<!-- Bubble overflow padding when there are no bubbles -->
|
||||||
@@ -154,7 +156,7 @@
|
|||||||
<!-- Interior padding of the message bubble -->
|
<!-- Interior padding of the message bubble -->
|
||||||
<dimen name="bubble_message_padding">4dp</dimen>
|
<dimen name="bubble_message_padding">4dp</dimen>
|
||||||
<!-- Offset between bubbles in their stacked position. -->
|
<!-- Offset between bubbles in their stacked position. -->
|
||||||
<dimen name="bubble_stack_offset">10dp</dimen>
|
<dimen name="bubble_stack_offset">12dp</dimen>
|
||||||
<!-- Offset between stack y and animation y for bubble swap. -->
|
<!-- Offset between stack y and animation y for bubble swap. -->
|
||||||
<dimen name="bubble_swap_animation_offset">15dp</dimen>
|
<dimen name="bubble_swap_animation_offset">15dp</dimen>
|
||||||
<!-- How far offscreen the bubble stack rests. There's some padding around the bubble so
|
<!-- How far offscreen the bubble stack rests. There's some padding around the bubble so
|
||||||
|
|||||||
@@ -23,15 +23,19 @@ import android.annotation.Nullable;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Outline;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PaintFlagsDrawFilter;
|
import android.graphics.PaintFlagsDrawFilter;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.PathParser;
|
import android.util.PathParser;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.android.launcher3.icons.DotRenderer;
|
import com.android.launcher3.icons.DotRenderer;
|
||||||
|
import com.android.launcher3.icons.IconNormalizer;
|
||||||
import com.android.wm.shell.animation.Interpolators;
|
import com.android.wm.shell.animation.Interpolators;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -104,6 +108,19 @@ public class BadgedImageView extends ImageView {
|
|||||||
|
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
setClickable(true);
|
setClickable(true);
|
||||||
|
setOutlineProvider(new ViewOutlineProvider() {
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline) {
|
||||||
|
BadgedImageView.this.getOutline(outline);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getOutline(Outline outline) {
|
||||||
|
final int bubbleSize = mPositioner.getBubbleSize();
|
||||||
|
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(bubbleSize);
|
||||||
|
final int inset = (bubbleSize - normalizedSize) / 2;
|
||||||
|
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize(BubblePositioner positioner) {
|
public void initialize(BubblePositioner positioner) {
|
||||||
@@ -111,7 +128,7 @@ public class BadgedImageView extends ImageView {
|
|||||||
|
|
||||||
Path iconPath = PathParser.createPathFromPathData(
|
Path iconPath = PathParser.createPathFromPathData(
|
||||||
getResources().getString(com.android.internal.R.string.config_icon_mask));
|
getResources().getString(com.android.internal.R.string.config_icon_mask));
|
||||||
mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
|
mDotRenderer = new DotRenderer(mPositioner.getBubbleSize(),
|
||||||
iconPath, DEFAULT_PATH_SIZE);
|
iconPath, DEFAULT_PATH_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +209,7 @@ public class BadgedImageView extends ImageView {
|
|||||||
* @param iconPath The new icon path to use when calculating dot position.
|
* @param iconPath The new icon path to use when calculating dot position.
|
||||||
*/
|
*/
|
||||||
void drawDot(Path iconPath) {
|
void drawDot(Path iconPath) {
|
||||||
mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
|
mDotRenderer = new DotRenderer(mPositioner.getBubbleSize(),
|
||||||
iconPath, DEFAULT_PATH_SIZE);
|
iconPath, DEFAULT_PATH_SIZE);
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -722,6 +722,7 @@ public class BubbleController {
|
|||||||
|| !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
|
|| !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
|
||||||
mDensityDpi = newConfig.densityDpi;
|
mDensityDpi = newConfig.densityDpi;
|
||||||
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
|
mScreenBounds.set(newConfig.windowConfiguration.getBounds());
|
||||||
|
mBubbleData.onMaxBubblesChanged();
|
||||||
mBubbleIconFactory = new BubbleIconFactory(mContext);
|
mBubbleIconFactory = new BubbleIconFactory(mContext);
|
||||||
mStackView.onDisplaySizeChanged();
|
mStackView.onDisplaySizeChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,9 +141,11 @@ public class BubbleData {
|
|||||||
private final BubbleOverflow mOverflow;
|
private final BubbleOverflow mOverflow;
|
||||||
private boolean mShowingOverflow;
|
private boolean mShowingOverflow;
|
||||||
private boolean mExpanded;
|
private boolean mExpanded;
|
||||||
private final int mMaxBubbles;
|
private int mMaxBubbles;
|
||||||
private int mMaxOverflowBubbles;
|
private int mMaxOverflowBubbles;
|
||||||
|
|
||||||
|
private boolean mNeedsTrimming;
|
||||||
|
|
||||||
// State tracked during an operation -- keeps track of what listener events to dispatch.
|
// State tracked during an operation -- keeps track of what listener events to dispatch.
|
||||||
private Update mStateChange;
|
private Update mStateChange;
|
||||||
|
|
||||||
@@ -180,7 +182,7 @@ public class BubbleData {
|
|||||||
mOverflowBubbles = new ArrayList<>();
|
mOverflowBubbles = new ArrayList<>();
|
||||||
mPendingBubbles = new HashMap<>();
|
mPendingBubbles = new HashMap<>();
|
||||||
mStateChange = new Update(mBubbles, mOverflowBubbles);
|
mStateChange = new Update(mBubbles, mOverflowBubbles);
|
||||||
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
|
mMaxBubbles = mPositioner.getMaxBubbles();
|
||||||
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
|
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +196,16 @@ public class BubbleData {
|
|||||||
mCancelledListener = listener;
|
mCancelledListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onMaxBubblesChanged() {
|
||||||
|
mMaxBubbles = mPositioner.getMaxBubbles();
|
||||||
|
if (!mExpanded) {
|
||||||
|
trim();
|
||||||
|
dispatchPendingChanges();
|
||||||
|
} else {
|
||||||
|
mNeedsTrimming = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasBubbles() {
|
public boolean hasBubbles() {
|
||||||
return !mBubbles.isEmpty();
|
return !mBubbles.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -455,13 +467,19 @@ public class BubbleData {
|
|||||||
|
|
||||||
private void trim() {
|
private void trim() {
|
||||||
if (mBubbles.size() > mMaxBubbles) {
|
if (mBubbles.size() > mMaxBubbles) {
|
||||||
|
int numtoRemove = mBubbles.size() - mMaxBubbles;
|
||||||
|
ArrayList<Bubble> toRemove = new ArrayList<>();
|
||||||
mBubbles.stream()
|
mBubbles.stream()
|
||||||
// sort oldest first (ascending lastActivity)
|
// sort oldest first (ascending lastActivity)
|
||||||
.sorted(Comparator.comparingLong(Bubble::getLastActivity))
|
.sorted(Comparator.comparingLong(Bubble::getLastActivity))
|
||||||
// skip the selected bubble
|
// skip the selected bubble
|
||||||
.filter((b) -> !b.equals(mSelectedBubble))
|
.filter((b) -> !b.equals(mSelectedBubble))
|
||||||
.findFirst()
|
.forEachOrdered((b) -> {
|
||||||
.ifPresent((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED));
|
if (toRemove.size() < numtoRemove) {
|
||||||
|
toRemove.add(b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toRemove.forEach((b) -> doRemove(b.getKey(), Bubbles.DISMISS_AGED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,6 +788,10 @@ public class BubbleData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mNeedsTrimming) {
|
||||||
|
mNeedsTrimming = false;
|
||||||
|
trim();
|
||||||
|
}
|
||||||
mExpanded = shouldExpand;
|
mExpanded = shouldExpand;
|
||||||
mStateChange.expanded = shouldExpand;
|
mStateChange.expanded = shouldExpand;
|
||||||
mStateChange.expandedChanged = true;
|
mStateChange.expandedChanged = true;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import android.widget.LinearLayout;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||||
|
import com.android.launcher3.icons.IconNormalizer;
|
||||||
import com.android.wm.shell.R;
|
import com.android.wm.shell.R;
|
||||||
import com.android.wm.shell.TaskView;
|
import com.android.wm.shell.TaskView;
|
||||||
import com.android.wm.shell.common.AlphaOptimizedButton;
|
import com.android.wm.shell.common.AlphaOptimizedButton;
|
||||||
@@ -726,9 +727,13 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
|
setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
|
||||||
|
|
||||||
final float expandedViewY = mPositioner.getExpandedViewY();
|
final float expandedViewY = mPositioner.getExpandedViewY();
|
||||||
|
// TODO: I don't understand why it works but it does - why normalized in portrait
|
||||||
|
// & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
|
||||||
|
final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
|
||||||
|
mPositioner.getBubbleSize());
|
||||||
final float bubbleCenter = showVertically
|
final float bubbleCenter = showVertically
|
||||||
? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
|
? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
|
||||||
: bubblePosition + (mPositioner.getBubbleBitmapSize() / 2f) - mPointerWidth;
|
: bubblePosition + (normalizedSize / 2f) - mPointerWidth;
|
||||||
// Post because we need the width of the view
|
// Post because we need the width of the view
|
||||||
post(() -> {
|
post(() -> {
|
||||||
float pointerY;
|
float pointerY;
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ public class BubbleFlyoutView extends FrameLayout {
|
|||||||
private final int mFlyoutSpaceFromBubble;
|
private final int mFlyoutSpaceFromBubble;
|
||||||
private final int mPointerSize;
|
private final int mPointerSize;
|
||||||
private int mBubbleSize;
|
private int mBubbleSize;
|
||||||
private int mBubbleBitmapSize;
|
|
||||||
|
|
||||||
private final int mFlyoutElevation;
|
private final int mFlyoutElevation;
|
||||||
private final int mBubbleElevation;
|
private final int mBubbleElevation;
|
||||||
@@ -337,10 +336,9 @@ public class BubbleFlyoutView extends FrameLayout {
|
|||||||
boolean hideDot,
|
boolean hideDot,
|
||||||
BubblePositioner positioner) {
|
BubblePositioner positioner) {
|
||||||
|
|
||||||
mBubbleBitmapSize = positioner.getBubbleBitmapSize();
|
|
||||||
mBubbleSize = positioner.getBubbleSize();
|
mBubbleSize = positioner.getBubbleSize();
|
||||||
|
|
||||||
mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
|
mOriginalDotSize = SIZE_PERCENTAGE * mBubbleSize;
|
||||||
mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
|
mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
|
||||||
mNewDotSize = mNewDotRadius * 2f;
|
mNewDotSize = mNewDotRadius * 2f;
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ public class BubbleIconFactory extends BaseIconFactory {
|
|||||||
|
|
||||||
public BubbleIconFactory(Context context) {
|
public BubbleIconFactory(Context context) {
|
||||||
super(context, context.getResources().getConfiguration().densityDpi,
|
super(context, context.getResources().getConfiguration().densityDpi,
|
||||||
context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
|
context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
|
||||||
mBadgeSize = mContext.getResources().getDimensionPixelSize(
|
mBadgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size);
|
||||||
com.android.launcher3.icons.R.dimen.profile_badge_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,12 +30,6 @@ import android.view.LayoutInflater
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import com.android.wm.shell.R
|
import com.android.wm.shell.R
|
||||||
|
|
||||||
/**
|
|
||||||
* The icon in the bubble overflow is scaled down, this is the percent of the normal bubble bitmap
|
|
||||||
* size to use.
|
|
||||||
*/
|
|
||||||
const val ICON_BITMAP_SIZE_PERCENT = 0.46f
|
|
||||||
|
|
||||||
class BubbleOverflow(
|
class BubbleOverflow(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val positioner: BubblePositioner
|
private val positioner: BubblePositioner
|
||||||
@@ -44,10 +38,9 @@ class BubbleOverflow(
|
|||||||
private lateinit var bitmap: Bitmap
|
private lateinit var bitmap: Bitmap
|
||||||
private lateinit var dotPath: Path
|
private lateinit var dotPath: Path
|
||||||
|
|
||||||
private var bitmapSize = 0
|
|
||||||
private var iconBitmapSize = 0
|
|
||||||
private var dotColor = 0
|
private var dotColor = 0
|
||||||
private var showDot = false
|
private var showDot = false
|
||||||
|
private var overflowIconInset = 0
|
||||||
|
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
private var expandedView: BubbleExpandedView?
|
private var expandedView: BubbleExpandedView?
|
||||||
@@ -55,8 +48,6 @@ class BubbleOverflow(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
updateResources()
|
updateResources()
|
||||||
bitmapSize = positioner.bubbleBitmapSize
|
|
||||||
iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt()
|
|
||||||
expandedView = null
|
expandedView = null
|
||||||
overflowBtn = null
|
overflowBtn = null
|
||||||
}
|
}
|
||||||
@@ -80,10 +71,10 @@ class BubbleOverflow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateResources() {
|
fun updateResources() {
|
||||||
bitmapSize = positioner.bubbleBitmapSize
|
overflowIconInset = context.resources.getDimensionPixelSize(
|
||||||
iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt()
|
R.dimen.bubble_overflow_icon_inset)
|
||||||
val bubbleSize = positioner.bubbleSize
|
overflowBtn?.layoutParams = FrameLayout.LayoutParams(positioner.bubbleSize,
|
||||||
overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize)
|
positioner.bubbleSize)
|
||||||
expandedView?.updateDimensions()
|
expandedView?.updateDimensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +94,7 @@ class BubbleOverflow(
|
|||||||
val iconFactory = BubbleIconFactory(context)
|
val iconFactory = BubbleIconFactory(context)
|
||||||
|
|
||||||
// Update bitmap
|
// Update bitmap
|
||||||
val fg = InsetDrawable(overflowBtn?.drawable,
|
val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
|
||||||
bitmapSize - iconBitmapSize /* inset */)
|
|
||||||
bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
|
bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
|
||||||
ColorDrawable(colorAccent), fg),
|
ColorDrawable(colorAccent), fg),
|
||||||
null /* user */, true /* shrinkNonAdaptiveIcons */).icon
|
null /* user */, true /* shrinkNonAdaptiveIcons */).icon
|
||||||
|
|||||||
@@ -56,20 +56,20 @@ public class BubblePositioner {
|
|||||||
public static final int TASKBAR_POSITION_LEFT = 1;
|
public static final int TASKBAR_POSITION_LEFT = 1;
|
||||||
public static final int TASKBAR_POSITION_BOTTOM = 2;
|
public static final int TASKBAR_POSITION_BOTTOM = 2;
|
||||||
|
|
||||||
/**
|
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
|
||||||
* The bitmap in the bubble is slightly smaller than the overall size of the bubble.
|
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
|
||||||
* This is the percentage to scale the image down based on the overall bubble size.
|
|
||||||
*/
|
|
||||||
private static final float BUBBLE_BITMAP_SIZE_PERCENT = 0.86f;
|
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private WindowManager mWindowManager;
|
private WindowManager mWindowManager;
|
||||||
private Rect mPositionRect;
|
private Rect mPositionRect;
|
||||||
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
|
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
|
||||||
private Insets mInsets;
|
private Insets mInsets;
|
||||||
|
private int mDefaultMaxBubbles;
|
||||||
|
private int mMaxBubbles;
|
||||||
|
|
||||||
private int mBubbleSize;
|
private int mBubbleSize;
|
||||||
private int mBubbleBitmapSize;
|
private int mBubbleBadgeSize;
|
||||||
|
private int mSpacingBetweenBubbles;
|
||||||
private int mExpandedViewLargeScreenWidth;
|
private int mExpandedViewLargeScreenWidth;
|
||||||
private int mExpandedViewPadding;
|
private int mExpandedViewPadding;
|
||||||
private int mPointerMargin;
|
private int mPointerMargin;
|
||||||
@@ -150,19 +150,47 @@ public class BubblePositioner {
|
|||||||
mPositionRect.bottom -= mInsets.bottom;
|
mPositionRect.bottom -= mInsets.bottom;
|
||||||
|
|
||||||
Resources res = mContext.getResources();
|
Resources res = mContext.getResources();
|
||||||
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
|
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
|
||||||
mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
|
mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
|
||||||
|
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
|
||||||
|
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
|
||||||
|
|
||||||
mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
|
mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
|
||||||
R.dimen.bubble_expanded_view_tablet_width);
|
R.dimen.bubble_expanded_view_tablet_width);
|
||||||
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
|
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
|
||||||
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
|
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
|
||||||
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
|
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
|
||||||
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
|
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
|
||||||
|
|
||||||
|
mMaxBubbles = calculateMaxBubbles();
|
||||||
|
|
||||||
if (mShowingInTaskbar) {
|
if (mShowingInTaskbar) {
|
||||||
adjustForTaskbar();
|
adjustForTaskbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the maximum number of bubbles that can fit on the screen when expanded. If the
|
||||||
|
* screen size / screen density is too small to support the default maximum number, then
|
||||||
|
* the number will be adjust to something lower to ensure everything is presented nicely.
|
||||||
|
*/
|
||||||
|
private int calculateMaxBubbles() {
|
||||||
|
// Use the shortest edge.
|
||||||
|
// In portrait the bubbles should align with the expanded view so subtract its padding.
|
||||||
|
// We always show the overflow so subtract one bubble size.
|
||||||
|
int padding = showBubblesVertically() ? 0 : (mExpandedViewPadding * 2);
|
||||||
|
int availableSpace = Math.min(mPositionRect.width(), mPositionRect.height())
|
||||||
|
- padding
|
||||||
|
- mBubbleSize;
|
||||||
|
// Each of the bubbles have spacing because the overflow is at the end.
|
||||||
|
int howManyFit = availableSpace / (mBubbleSize + mSpacingBetweenBubbles);
|
||||||
|
if (howManyFit < mDefaultMaxBubbles) {
|
||||||
|
// Not enough space for the default.
|
||||||
|
return howManyFit;
|
||||||
|
}
|
||||||
|
return mDefaultMaxBubbles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
|
* Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
|
||||||
* not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
|
* not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
|
||||||
@@ -225,20 +253,16 @@ public class BubblePositioner {
|
|||||||
return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
|
return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Size of the bubble account for badge & dot. */
|
/** Size of the bubble. */
|
||||||
public int getBubbleSize() {
|
public int getBubbleSize() {
|
||||||
int bsize = (mShowingInTaskbar && mTaskbarIconSize > 0)
|
return (mShowingInTaskbar && mTaskbarIconSize > 0)
|
||||||
? mTaskbarIconSize
|
? mTaskbarIconSize
|
||||||
: mBubbleSize;
|
: mBubbleSize;
|
||||||
return bsize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Size of the bitmap within the bubble */
|
/** The maximum number of bubbles that can be displayed comfortably on screen. */
|
||||||
public int getBubbleBitmapSize() {
|
public int getMaxBubbles() {
|
||||||
float size = (mShowingInTaskbar && mTaskbarIconSize > 0)
|
return mMaxBubbles;
|
||||||
? (mTaskbarIconSize * BUBBLE_BITMAP_SIZE_PERCENT)
|
|
||||||
: mBubbleBitmapSize;
|
|
||||||
return (int) size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|||||||
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
|
||||||
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||||
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
@@ -242,7 +243,6 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
@Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
|
@Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
|
||||||
|
|
||||||
private int mMaxBubbles;
|
|
||||||
private int mBubbleSize;
|
private int mBubbleSize;
|
||||||
private int mBubbleElevation;
|
private int mBubbleElevation;
|
||||||
private int mBubbleTouchPadding;
|
private int mBubbleTouchPadding;
|
||||||
@@ -632,6 +632,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
|
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
|
||||||
// bubble since it's stuck to the target.
|
// bubble since it's stuck to the target.
|
||||||
if (!passEventToMagnetizedObject(ev)) {
|
if (!passEventToMagnetizedObject(ev)) {
|
||||||
|
updateBubbleShadows(true /* showForAllBubbles */);
|
||||||
if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
|
if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
|
||||||
mExpandedAnimationController.dragBubbleOut(
|
mExpandedAnimationController.dragBubbleOut(
|
||||||
v, viewInitialX + dx, viewInitialY + dy);
|
v, viewInitialX + dx, viewInitialY + dy);
|
||||||
@@ -671,7 +672,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mStackAnimationController.flingStackThenSpringToEdge(
|
mStackAnimationController.flingStackThenSpringToEdge(
|
||||||
viewInitialX + dx, velX, velY) <= 0;
|
viewInitialX + dx, velX, velY) <= 0;
|
||||||
final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe;
|
final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe;
|
||||||
updateBadgesAndZOrder(updateForCollapsedStack);
|
updateBadges(updateForCollapsedStack);
|
||||||
logBubbleEvent(null /* no bubble associated with bubble stack move */,
|
logBubbleEvent(null /* no bubble associated with bubble stack move */,
|
||||||
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
|
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
|
||||||
}
|
}
|
||||||
@@ -762,8 +763,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mBubbleData = data;
|
mBubbleData = data;
|
||||||
|
|
||||||
Resources res = getResources();
|
Resources res = getResources();
|
||||||
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
|
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
|
||||||
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
|
|
||||||
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||||
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
|
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
|
||||||
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
|
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
|
||||||
@@ -783,9 +783,9 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mBubbleController.onAllBubblesAnimatedOut();
|
mBubbleController.onAllBubblesAnimatedOut();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mStackAnimationController = new StackAnimationController(
|
mStackAnimationController = new StackAnimationController(
|
||||||
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut, mPositioner);
|
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut,
|
||||||
|
this::animateShadows /* onStackAnimationFinished */, mPositioner);
|
||||||
|
|
||||||
mExpandedAnimationController = new ExpandedAnimationController(
|
mExpandedAnimationController = new ExpandedAnimationController(
|
||||||
mPositioner, mExpandedViewPadding, onBubbleAnimatedOut);
|
mPositioner, mExpandedViewPadding, onBubbleAnimatedOut);
|
||||||
@@ -1505,13 +1505,12 @@ public class BubbleStackView extends FrameLayout
|
|||||||
// Set the dot position to the opposite of the side the stack is resting on, since the stack
|
// Set the dot position to the opposite of the side the stack is resting on, since the stack
|
||||||
// resting slightly off-screen would result in the dot also being off-screen.
|
// resting slightly off-screen would result in the dot also being off-screen.
|
||||||
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
|
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
|
||||||
|
|
||||||
bubble.getIconView().setOnClickListener(mBubbleClickListener);
|
bubble.getIconView().setOnClickListener(mBubbleClickListener);
|
||||||
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
|
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
|
||||||
|
|
||||||
mBubbleContainer.addView(bubble.getIconView(), 0,
|
mBubbleContainer.addView(bubble.getIconView(), 0,
|
||||||
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
|
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
|
||||||
mPositioner.getBubbleSize()));
|
mPositioner.getBubbleSize()));
|
||||||
|
updateBubbleShadows(false /* showForAllBubbles */);
|
||||||
animateInFlyoutForBubble(bubble);
|
animateInFlyoutForBubble(bubble);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
|
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
|
||||||
@@ -1566,7 +1565,8 @@ public class BubbleStackView extends FrameLayout
|
|||||||
};
|
};
|
||||||
if (mIsExpanded || isExpansionAnimating()) {
|
if (mIsExpanded || isExpansionAnimating()) {
|
||||||
reorder.run();
|
reorder.run();
|
||||||
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
|
updateBadges(false /* setBadgeForCollapsedStack */);
|
||||||
|
updateZOrder();
|
||||||
} else if (!isExpansionAnimating()) {
|
} else if (!isExpansionAnimating()) {
|
||||||
List<View> bubbleViews = bubbles.stream()
|
List<View> bubbleViews = bubbles.stream()
|
||||||
.map(b -> b.getIconView()).collect(Collectors.toList());
|
.map(b -> b.getIconView()).collect(Collectors.toList());
|
||||||
@@ -1803,7 +1803,8 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
beforeExpandedViewAnimation();
|
beforeExpandedViewAnimation();
|
||||||
|
|
||||||
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
|
updateZOrder();
|
||||||
|
updateBadges(false /* setBadgeForCollapsedStack */);
|
||||||
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
||||||
updateOverflowVisibility();
|
updateOverflowVisibility();
|
||||||
updatePointerPosition();
|
updatePointerPosition();
|
||||||
@@ -2014,7 +2015,8 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mExpandedBubble));
|
mExpandedBubble));
|
||||||
}
|
}
|
||||||
updateOverflowVisibility();
|
updateOverflowVisibility();
|
||||||
updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
|
updateZOrder();
|
||||||
|
updateBadges(true /* setBadgeForCollapsedStack */);
|
||||||
afterExpandedViewAnimation();
|
afterExpandedViewAnimation();
|
||||||
if (previouslySelected != null) {
|
if (previouslySelected != null) {
|
||||||
previouslySelected.setTaskViewVisibility(false);
|
previouslySelected.setTaskViewVisibility(false);
|
||||||
@@ -2707,14 +2709,56 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the appropriate Z-order, badge, and dot position for each bubble in the stack.
|
* Updates whether each of the bubbles should show shadows. When collapsed & resting, only the
|
||||||
* Animate dot and badge changes.
|
* visible bubbles (top 2) will show a shadow. When the stack is being dragged, everything
|
||||||
|
* shows a shadow. When an individual bubble is dragged out, it should show a shadow.
|
||||||
*/
|
*/
|
||||||
private void updateBadgesAndZOrder(boolean setBadgeForCollapsedStack) {
|
private void updateBubbleShadows(boolean showForAllBubbles) {
|
||||||
|
int bubbleCount = getBubbleCount();
|
||||||
|
for (int i = 0; i < bubbleCount; i++) {
|
||||||
|
final float z = (mPositioner.getMaxBubbles() * mBubbleElevation) - i;
|
||||||
|
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||||
|
boolean isDraggedOut = mMagnetizedObject != null
|
||||||
|
&& mMagnetizedObject.getUnderlyingObject().equals(bv);
|
||||||
|
if (showForAllBubbles || isDraggedOut) {
|
||||||
|
bv.setZ(z);
|
||||||
|
} else {
|
||||||
|
final float tz = i < NUM_VISIBLE_WHEN_RESTING ? z : 0f;
|
||||||
|
bv.setZ(tz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the bubbles are flung and then rest, the shadows stack up for the bubbles hidden
|
||||||
|
* beneath the top two bubbles, to avoid this we animate the Z translations once the stack
|
||||||
|
* is resting so that they fade away nicely.
|
||||||
|
*/
|
||||||
|
private void animateShadows() {
|
||||||
|
int bubbleCount = getBubbleCount();
|
||||||
|
for (int i = 0; i < bubbleCount; i++) {
|
||||||
|
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||||
|
boolean fullShadow = i < NUM_VISIBLE_WHEN_RESTING;
|
||||||
|
if (!fullShadow) {
|
||||||
|
bv.animate().translationZ(0).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateZOrder() {
|
||||||
|
int bubbleCount = getBubbleCount();
|
||||||
|
for (int i = 0; i < bubbleCount; i++) {
|
||||||
|
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||||
|
bv.setZ(i < NUM_VISIBLE_WHEN_RESTING
|
||||||
|
? (mPositioner.getMaxBubbles() * mBubbleElevation) - i
|
||||||
|
: 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBadges(boolean setBadgeForCollapsedStack) {
|
||||||
int bubbleCount = getBubbleCount();
|
int bubbleCount = getBubbleCount();
|
||||||
for (int i = 0; i < bubbleCount; i++) {
|
for (int i = 0; i < bubbleCount; i++) {
|
||||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||||
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
|
|
||||||
if (mIsExpanded) {
|
if (mIsExpanded) {
|
||||||
// If we're not displaying vertically, we always show the badge on the left.
|
// If we're not displaying vertically, we always show the badge on the left.
|
||||||
boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
|
boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
|
|||||||
post {
|
post {
|
||||||
with(view) {
|
with(view) {
|
||||||
val bubbleSize = context.resources.getDimensionPixelSize(
|
val bubbleSize = context.resources.getDimensionPixelSize(
|
||||||
R.dimen.individual_bubble_size)
|
R.dimen.bubble_size)
|
||||||
translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
|
translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
|
||||||
}
|
}
|
||||||
animate()
|
animate()
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.wm.shell.bubbles.animation;
|
package com.android.wm.shell.bubbles.animation;
|
||||||
|
|
||||||
|
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
@@ -209,16 +211,8 @@ public class ExpandedAnimationController
|
|||||||
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
||||||
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
||||||
mBubbleSizePx = mPositioner.getBubbleSize();
|
mBubbleSizePx = mPositioner.getBubbleSize();
|
||||||
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
|
mBubblesMaxRendered = mPositioner.getMaxBubbles();
|
||||||
mBubblesMaxSpace = res.getDimensionPixelSize(R.dimen.bubble_max_spacing);
|
mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
|
||||||
final float availableSpace = mPositioner.isLandscape()
|
|
||||||
? mPositioner.getAvailableRect().height()
|
|
||||||
: mPositioner.getAvailableRect().width();
|
|
||||||
final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
|
|
||||||
+ (mBubblesMaxRendered + 1) * mBubbleSizePx;
|
|
||||||
float spaceBetweenBubbles =
|
|
||||||
(availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
|
|
||||||
mSpaceBetweenBubbles = Math.min(spaceBetweenBubbles, mBubblesMaxSpace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -289,7 +283,8 @@ public class ExpandedAnimationController
|
|||||||
path.lineTo(stackedX, expandedY);
|
path.lineTo(stackedX, expandedY);
|
||||||
|
|
||||||
// Then, draw a line down to the stack position.
|
// Then, draw a line down to the stack position.
|
||||||
path.lineTo(stackedX, mCollapsePoint.y + Math.min(index, 1) * mStackOffsetPx);
|
path.lineTo(stackedX, mCollapsePoint.y
|
||||||
|
+ Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The lead bubble should be the bubble with the longest distance to travel when we're
|
// The lead bubble should be the bubble with the longest distance to travel when we're
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.wm.shell.bubbles.animation;
|
package com.android.wm.shell.bubbles.animation;
|
||||||
|
|
||||||
|
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
@@ -182,9 +184,7 @@ public class StackAnimationController extends
|
|||||||
private int mMaxBubbles;
|
private int mMaxBubbles;
|
||||||
/** Default bubble elevation. */
|
/** Default bubble elevation. */
|
||||||
private int mElevation;
|
private int mElevation;
|
||||||
/** Diameter of the bubble icon. */
|
/** Diameter of the bubble. */
|
||||||
private int mBubbleBitmapSize;
|
|
||||||
/** Width of the bubble (icon and padding). */
|
|
||||||
private int mBubbleSize;
|
private int mBubbleSize;
|
||||||
/**
|
/**
|
||||||
* The amount of space to add between the bubbles and certain UI elements, such as the top of
|
* The amount of space to add between the bubbles and certain UI elements, such as the top of
|
||||||
@@ -257,14 +257,21 @@ public class StackAnimationController extends
|
|||||||
*/
|
*/
|
||||||
private Runnable mOnBubbleAnimatedOutAction;
|
private Runnable mOnBubbleAnimatedOutAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to run whenever the stack is finished being flung somewhere.
|
||||||
|
*/
|
||||||
|
private Runnable mOnStackAnimationFinished;
|
||||||
|
|
||||||
public StackAnimationController(
|
public StackAnimationController(
|
||||||
FloatingContentCoordinator floatingContentCoordinator,
|
FloatingContentCoordinator floatingContentCoordinator,
|
||||||
IntSupplier bubbleCountSupplier,
|
IntSupplier bubbleCountSupplier,
|
||||||
Runnable onBubbleAnimatedOutAction,
|
Runnable onBubbleAnimatedOutAction,
|
||||||
|
Runnable onStackAnimationFinished,
|
||||||
BubblePositioner positioner) {
|
BubblePositioner positioner) {
|
||||||
mFloatingContentCoordinator = floatingContentCoordinator;
|
mFloatingContentCoordinator = floatingContentCoordinator;
|
||||||
mBubbleCountSupplier = bubbleCountSupplier;
|
mBubbleCountSupplier = bubbleCountSupplier;
|
||||||
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
|
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
|
||||||
|
mOnStackAnimationFinished = onStackAnimationFinished;
|
||||||
mPositioner = positioner;
|
mPositioner = positioner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +309,7 @@ public class StackAnimationController extends
|
|||||||
return true; // Default to left, which is where it starts by default.
|
return true; // Default to left, which is where it starts by default.
|
||||||
}
|
}
|
||||||
|
|
||||||
float stackCenter = mStackPosition.x + mBubbleBitmapSize / 2;
|
float stackCenter = mStackPosition.x + mBubbleSize / 2;
|
||||||
float screenCenter = mLayout.getWidth() / 2;
|
float screenCenter = mLayout.getWidth() / 2;
|
||||||
return stackCenter < screenCenter;
|
return stackCenter < screenCenter;
|
||||||
}
|
}
|
||||||
@@ -346,7 +353,7 @@ public class StackAnimationController extends
|
|||||||
* @return The X value that the stack will end up at after the fling/spring.
|
* @return The X value that the stack will end up at after the fling/spring.
|
||||||
*/
|
*/
|
||||||
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
|
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
|
||||||
final boolean stackOnLeftSide = x - mBubbleBitmapSize / 2 < mLayout.getWidth() / 2;
|
final boolean stackOnLeftSide = x - mBubbleSize / 2 < mLayout.getWidth() / 2;
|
||||||
|
|
||||||
final boolean stackShouldFlingLeft = stackOnLeftSide
|
final boolean stackShouldFlingLeft = stackOnLeftSide
|
||||||
? velX < ESCAPE_VELOCITY
|
? velX < ESCAPE_VELOCITY
|
||||||
@@ -674,6 +681,10 @@ public class StackAnimationController extends
|
|||||||
mPositioner.setRestingPosition(mStackPosition);
|
mPositioner.setRestingPosition(mStackPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mOnStackAnimationFinished != null) {
|
||||||
|
mOnStackAnimationFinished.run();
|
||||||
|
}
|
||||||
|
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
for (Runnable callback : after) {
|
for (Runnable callback : after) {
|
||||||
callback.run();
|
callback.run();
|
||||||
@@ -718,7 +729,7 @@ public class StackAnimationController extends
|
|||||||
} else {
|
} else {
|
||||||
// We only show the first two bubbles in the stack & the rest hide behind them
|
// We only show the first two bubbles in the stack & the rest hide behind them
|
||||||
// so they don't need an offset.
|
// so they don't need an offset.
|
||||||
return index > 1 ? 0f : mStackOffset;
|
return index > (NUM_VISIBLE_WHEN_RESTING - 1) ? 0f : mStackOffset;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0f;
|
return 0f;
|
||||||
@@ -827,7 +838,8 @@ public class StackAnimationController extends
|
|||||||
private void moveToFinalIndex(View view, int newIndex,
|
private void moveToFinalIndex(View view, int newIndex,
|
||||||
Runnable finishReorder) {
|
Runnable finishReorder) {
|
||||||
final ViewPropertyAnimator animator = view.animate()
|
final ViewPropertyAnimator animator = view.animate()
|
||||||
.translationY(getStackPosition().y + Math.min(newIndex, 1) * mStackOffset)
|
.translationY(getStackPosition().y
|
||||||
|
+ Math.min(newIndex, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffset)
|
||||||
.setDuration(BUBBLE_SWAP_DURATION)
|
.setDuration(BUBBLE_SWAP_DURATION)
|
||||||
.withEndAction(() -> {
|
.withEndAction(() -> {
|
||||||
view.setTag(R.id.reorder_animator_tag, null);
|
view.setTag(R.id.reorder_animator_tag, null);
|
||||||
@@ -836,8 +848,9 @@ public class StackAnimationController extends
|
|||||||
view.setTag(R.id.reorder_animator_tag, animator);
|
view.setTag(R.id.reorder_animator_tag, animator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: do we need this & BubbleStackView#updateBadgesAndZOrder?
|
||||||
private void updateBadgesAndZOrder(View v, int index) {
|
private void updateBadgesAndZOrder(View v, int index) {
|
||||||
v.setZ((mMaxBubbles * mElevation) - index);
|
v.setZ(index < NUM_VISIBLE_WHEN_RESTING ? (mMaxBubbles * mElevation) - index : 0f);
|
||||||
BadgedImageView bv = (BadgedImageView) v;
|
BadgedImageView bv = (BadgedImageView) v;
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
bv.showDotAndBadge(!isStackOnLeftSide());
|
bv.showDotAndBadge(!isStackOnLeftSide());
|
||||||
@@ -857,7 +870,6 @@ public class StackAnimationController extends
|
|||||||
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
|
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
|
||||||
mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||||
mBubbleSize = mPositioner.getBubbleSize();
|
mBubbleSize = mPositioner.getBubbleSize();
|
||||||
mBubbleBitmapSize = mPositioner.getBubbleBitmapSize();
|
|
||||||
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
||||||
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
|
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
|
||||||
}
|
}
|
||||||
@@ -942,8 +954,9 @@ public class StackAnimationController extends
|
|||||||
final float yOffset = getOffsetForChainedPropertyAnimation(
|
final float yOffset = getOffsetForChainedPropertyAnimation(
|
||||||
DynamicAnimation.TRANSLATION_Y, 0);
|
DynamicAnimation.TRANSLATION_Y, 0);
|
||||||
for (int i = 0; i < mLayout.getChildCount(); i++) {
|
for (int i = 0; i < mLayout.getChildCount(); i++) {
|
||||||
mLayout.getChildAt(i).setTranslationX(pos.x + (Math.min(i, 1) * xOffset));
|
float index = Math.min(i, NUM_VISIBLE_WHEN_RESTING - 1);
|
||||||
mLayout.getChildAt(i).setTranslationY(pos.y + (Math.min(i, 1) * yOffset));
|
mLayout.getChildAt(i).setTranslationX(pos.x + (index * xOffset));
|
||||||
|
mLayout.getChildAt(i).setTranslationY(pos.y + (index * yOffset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ package com.android.wm.shell.bubbles;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertNotNull;
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
@@ -90,6 +93,7 @@ public class BubbleDataTest extends ShellTestCase {
|
|||||||
private Bubble mBubbleDismissed;
|
private Bubble mBubbleDismissed;
|
||||||
|
|
||||||
private BubbleData mBubbleData;
|
private BubbleData mBubbleData;
|
||||||
|
private TestableBubblePositioner mPositioner;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private TimeSource mTimeSource;
|
private TimeSource mTimeSource;
|
||||||
@@ -150,9 +154,9 @@ public class BubbleDataTest extends ShellTestCase {
|
|||||||
mMainExecutor);
|
mMainExecutor);
|
||||||
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
|
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
|
||||||
mMainExecutor);
|
mMainExecutor);
|
||||||
TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
|
mPositioner = new TestableBubblePositioner(mContext,
|
||||||
mock(WindowManager.class));
|
mock(WindowManager.class));
|
||||||
mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner,
|
mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
|
||||||
mMainExecutor);
|
mMainExecutor);
|
||||||
|
|
||||||
// Used by BubbleData to set lastAccessedTime
|
// Used by BubbleData to set lastAccessedTime
|
||||||
@@ -810,6 +814,61 @@ public class BubbleDataTest extends ShellTestCase {
|
|||||||
assertEquals(1, mBubbleData.getOverflowBubbles().size());
|
assertEquals(1, mBubbleData.getOverflowBubbles().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_onMaxBubblesChanged_notExpanded() {
|
||||||
|
mBubbleData.setListener(mListener);
|
||||||
|
mPositioner.setMaxBubbles(5);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA1, 1000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA2, 2000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA3, 3000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryB1, 4000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryB2, 5000);
|
||||||
|
mBubbleData.setExpanded(false);
|
||||||
|
reset(mListener);
|
||||||
|
|
||||||
|
mPositioner.setMaxBubbles(3);
|
||||||
|
mBubbleData.onMaxBubblesChanged();
|
||||||
|
verifyUpdateReceived();
|
||||||
|
|
||||||
|
BubbleData.Update update = mUpdateCaptor.getValue();
|
||||||
|
assertThat(update.removedBubbles.get(0)).isEqualTo(
|
||||||
|
Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
|
||||||
|
assertThat(update.removedBubbles.get(1)).isEqualTo(
|
||||||
|
Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
|
||||||
|
|
||||||
|
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
|
||||||
|
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_onMaxBubblesChanged_expanded() {
|
||||||
|
mBubbleData.setListener(mListener);
|
||||||
|
mPositioner.setMaxBubbles(5);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA1, 1000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA2, 2000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryA3, 3000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryB1, 4000);
|
||||||
|
sendUpdatedEntryAtTime(mEntryB2, 5000);
|
||||||
|
mBubbleData.setExpanded(true);
|
||||||
|
reset(mListener);
|
||||||
|
|
||||||
|
mPositioner.setMaxBubbles(3);
|
||||||
|
mBubbleData.onMaxBubblesChanged();
|
||||||
|
verify(mListener, never()).applyUpdate(any());
|
||||||
|
|
||||||
|
mBubbleData.setExpanded(false);
|
||||||
|
verifyUpdateReceived();
|
||||||
|
|
||||||
|
BubbleData.Update update = mUpdateCaptor.getValue();
|
||||||
|
assertThat(update.removedBubbles.get(0)).isEqualTo(
|
||||||
|
Pair.create(mBubbleA1, Bubbles.DISMISS_AGED));
|
||||||
|
assertThat(update.removedBubbles.get(1)).isEqualTo(
|
||||||
|
Pair.create(mBubbleA2, Bubbles.DISMISS_AGED));
|
||||||
|
|
||||||
|
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA1.getKey()));
|
||||||
|
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
private void verifyUpdateReceived() {
|
private void verifyUpdateReceived() {
|
||||||
verify(mListener).applyUpdate(mUpdateCaptor.capture());
|
verify(mListener).applyUpdate(mUpdateCaptor.capture());
|
||||||
reset(mListener);
|
reset(mListener);
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
|
|||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
when(mPositioner.getBubbleBitmapSize()).thenReturn(40);
|
|
||||||
when(mPositioner.getBubbleSize()).thenReturn(60);
|
when(mPositioner.getBubbleSize()).thenReturn(60);
|
||||||
|
|
||||||
mFlyoutMessage = new Bubble.FlyoutMessage();
|
mFlyoutMessage = new Bubble.FlyoutMessage();
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ import android.graphics.Insets;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.android.wm.shell.R;
|
||||||
|
|
||||||
public class TestableBubblePositioner extends BubblePositioner {
|
public class TestableBubblePositioner extends BubblePositioner {
|
||||||
|
private int mMaxBubbles;
|
||||||
|
|
||||||
public TestableBubblePositioner(Context context,
|
public TestableBubblePositioner(Context context,
|
||||||
WindowManager windowManager) {
|
WindowManager windowManager) {
|
||||||
@@ -31,5 +34,15 @@ public class TestableBubblePositioner extends BubblePositioner {
|
|||||||
updateInternal(Configuration.ORIENTATION_PORTRAIT,
|
updateInternal(Configuration.ORIENTATION_PORTRAIT,
|
||||||
Insets.of(0, 0, 0, 0),
|
Insets.of(0, 0, 0, 0),
|
||||||
new Rect(0, 0, 500, 1000));
|
new Rect(0, 0, 500, 1000));
|
||||||
|
mMaxBubbles = context.getResources().getInteger(R.integer.bubbles_max_rendered);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxBubbles(int max) {
|
||||||
|
mMaxBubbles = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxBubbles() {
|
||||||
|
return mMaxBubbles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
|
|||||||
public int getAsInt() {
|
public int getAsInt() {
|
||||||
return mLayout.getChildCount();
|
return mLayout.getChildCount();
|
||||||
}
|
}
|
||||||
}, mock(Runnable.class)));
|
}, mock(Runnable.class), mock(Runnable.class)));
|
||||||
mLayout.setActiveController(mStackController);
|
mLayout.setActiveController(mStackController);
|
||||||
addOneMoreThanBubbleLimitBubbles();
|
addOneMoreThanBubbleLimitBubbles();
|
||||||
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
||||||
@@ -307,10 +307,12 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
|
|||||||
TestableStackController(
|
TestableStackController(
|
||||||
FloatingContentCoordinator floatingContentCoordinator,
|
FloatingContentCoordinator floatingContentCoordinator,
|
||||||
IntSupplier bubbleCountSupplier,
|
IntSupplier bubbleCountSupplier,
|
||||||
Runnable onBubbleAnimatedOutAction) {
|
Runnable onBubbleAnimatedOutAction,
|
||||||
|
Runnable onStackAnimationFinished) {
|
||||||
super(floatingContentCoordinator,
|
super(floatingContentCoordinator,
|
||||||
bubbleCountSupplier,
|
bubbleCountSupplier,
|
||||||
onBubbleAnimatedOutAction,
|
onBubbleAnimatedOutAction,
|
||||||
|
onStackAnimationFinished,
|
||||||
new TestableBubblePositioner(mContext, mock(WindowManager.class)));
|
new TestableBubblePositioner(mContext, mock(WindowManager.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user