Merge "Bubble bar drag to dismiss" into udc-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 056fc74..6818db6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,8 +31,11 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static java.lang.Math.abs;
+
import android.annotation.BinderThread;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -243,17 +246,21 @@
BUBBLE_STATE_EXECUTOR.execute(() -> {
createAndAddOverflowIfNeeded();
if (update.addedBubble != null) {
- viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+ viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+ null /* existingBubble */);
}
if (update.updatedBubble != null) {
+ BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
viewUpdate.updatedBubble =
- populateBubble(update.updatedBubble, mContext, mBarView);
+ populateBubble(mContext, update.updatedBubble, mBarView,
+ existingBubble);
}
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
for (int i = 0; i < update.currentBubbleList.size(); i++) {
BubbleBarBubble b =
- populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
+ populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
+ null /* existingBubble */);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
@@ -315,9 +322,11 @@
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
if (update.updatedBubble != null) {
- // TODO: (b/269670235) handle updates:
- // (1) if content / icons change -- requires reload & add back in place
- // (2) if showing update dot changes -- tell the view to hide / show the dot
+ // Updates mean the dot state may have changed; any other changes were updated in
+ // the populateBubble step.
+ BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
+ // If we're not stashed, we're visible so animate
+ bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -334,8 +343,8 @@
// TODO: (b/273316505) handle suppression
}
if (update.selectedBubbleKey != null) {
- if (mSelectedBubble != null
- && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+ if (mSelectedBubble == null
+ || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
if (newlySelected != null) {
bubbleToSelect = newlySelected;
@@ -360,12 +369,17 @@
/** Tells WMShell to show the currently selected bubble. */
public void showSelectedBubble() {
if (getSelectedBubbleKey() != null) {
- int[] bubbleBarCoords = mBarView.getLocationOnScreen();
if (mSelectedBubble instanceof BubbleBarBubble) {
- // TODO (b/269670235): hide the update dot on the view if needed.
+ // Because we've visited this bubble, we should suppress the notification.
+ // This is updated on WMShell side when we show the bubble, but that update isn't
+ // passed to launcher, instead we apply it directly here.
+ BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
+ info.setFlags(
+ info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ mSelectedBubble.getView().updateDotVisibility(true /* animate */);
}
mSystemUiProxy.showBubble(getSelectedBubbleKey(),
- bubbleBarCoords[0], bubbleBarCoords[1]);
+ getBubbleBarOffsetX(), getBubbleBarOffsetY());
} else {
Log.w(TAG, "Trying to show the selected bubble but it's null");
}
@@ -407,7 +421,8 @@
//
@Nullable
- private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+ private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
+ @Nullable BubbleBarBubble existingBubble) {
String appName;
Bitmap badgeBitmap;
Bitmap bubbleBitmap;
@@ -476,16 +491,27 @@
iconPath.transform(matrix);
dotPath = iconPath;
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
+ Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
- LayoutInflater inflater = LayoutInflater.from(context);
- BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+ if (existingBubble == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
- BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
- bubbleView.setBubble(bubble);
- return bubble;
+ BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ bubbleView.setBubble(bubble);
+ return bubble;
+ } else {
+ // If we already have a bubble (so it already has an inflated view), update it.
+ existingBubble.setInfo(b);
+ existingBubble.setBadge(badgeBitmap);
+ existingBubble.setIcon(bubbleBitmap);
+ existingBubble.setDotColor(dotColor);
+ existingBubble.setDotPath(dotPath);
+ existingBubble.setAppName(appName);
+ return existingBubble;
+ }
}
private BubbleBarOverflow createOverflow(Context context) {
@@ -520,4 +546,13 @@
return mIconFactory.createBadgedIconBitmap(drawable).icon;
}
+
+ private int getBubbleBarOffsetY() {
+ final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
+ return translation + mBarView.getHeight();
+ }
+
+ private int getBubbleBarOffsetX() {
+ return mBarView.getWidth() + mBarView.getHorizontalMargin();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 582dcc7..43e21f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -20,18 +20,18 @@
import com.android.wm.shell.common.bubbles.BubbleInfo
/** An entity in the bubble bar. */
-sealed class BubbleBarItem(open val key: String, open val view: BubbleView)
+sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
data class BubbleBarBubble(
- val info: BubbleInfo,
- override val view: BubbleView,
- val badge: Bitmap,
- val icon: Bitmap,
- val dotColor: Int,
- val dotPath: Path,
- val appName: String
+ var info: BubbleInfo,
+ override var view: BubbleView,
+ var badge: Bitmap,
+ var icon: Bitmap,
+ var dotColor: Int,
+ var dotPath: Path,
+ var appName: String
) : BubbleBarItem(info.key, view)
/** Represents the overflow bubble in the bubble bar. */
-data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("Overflow", view)
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index e0e181c..e93d410 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -255,6 +255,12 @@
setLayoutParams(lp);
}
+ /** @return the horizontal margin between the bubble bar and the edge of the screen. */
+ int getHorizontalMargin() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ return lp.getMarginEnd();
+ }
+
/**
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
@@ -266,6 +272,7 @@
final float collapsedWidth = collapsedWidth();
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ final boolean animate = getVisibility() == VISIBLE;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
@@ -283,16 +290,14 @@
if (widthState == 1f) {
bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
}
- bv.showBadge();
+ // When we're expanded, we're not stacked so we're not behind the stack
+ bv.setBehindStack(false, animate);
} else {
final float targetX = currentWidth - collapsedWidth + collapsedX;
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
- if (i > 0) {
- bv.hideBadge();
- } else {
- bv.showBadge();
- }
+ // If we're not the first bubble we're behind the stack
+ bv.setBehindStack(i > 0, animate);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 5177d93..bd660be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -125,6 +125,12 @@
public void setBubblesShowingOnHome(boolean onHome) {
if (mBubblesShowingOnHome != onHome) {
mBubblesShowingOnHome = onHome;
+
+ if (!mBarViewController.isBubbleBarVisible()) {
+ // if the bubble bar is not visible, there are no bubbles, so just return.
+ return;
+ }
+
if (mBubblesShowingOnHome) {
showBubbleBar(/* expanded= */ false);
// When transitioning from app to home the stash animator may already have been
@@ -309,4 +315,9 @@
return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
hotseatCellHeight - mUnstashedHeight) / 2;
}
+
+ float getBubbleBarTranslationY() {
+ return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+ : getBubbleBarTranslationYForTaskbar();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 92b76a6..12cb8c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -18,7 +18,9 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,10 +30,13 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
+import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.animation.Interpolators;
+
+import java.util.EnumSet;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
-// TODO: (b/269670235) currently this doesn't show the 'update dot'
/**
* View that displays a bubble icon, along with an app badge on either the left or
@@ -39,14 +44,42 @@
*/
public class BubbleView extends ConstraintLayout {
- // TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
public static final int DEFAULT_PATH_SIZE = 100;
+ /**
+ * 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 final int mBubbleSize;
+ private DotRenderer mDotRenderer;
+ private DotRenderer.DrawParams mDrawParams;
+ private int mDotColor;
+ private Rect mTempBounds = new Rect();
+
+ // Whether the dot is animating
+ private boolean mDotIsAnimating;
+ // What scale value the dot is animating to
+ private float mAnimatingToDotScale;
+ // The current scale value of the dot
+ private float mDotScale;
+
// 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;
@@ -75,6 +108,8 @@
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
+ mDrawParams = new DotRenderer.DrawParams();
+
setFocusable(true);
setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -91,17 +126,43 @@
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (!shouldDrawDot()) {
+ return;
+ }
+
+ getDrawingRect(mTempBounds);
+
+ mDrawParams.dotColor = mDotColor;
+ mDrawParams.iconBounds = mTempBounds;
+ mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.scale = mDotScale;
+
+ mDotRenderer.draw(canvas, mDrawParams);
+ }
+
/** Sets the bubble being rendered in this view. */
void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
+ mDotColor = bubble.getDotColor();
+ mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
}
+ /**
+ * 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.
+ */
void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
mBubble = overflow;
mBubbleIcon.setImageBitmap(bitmap);
- hideBadge();
+ mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
}
/** Returns the bubble being rendered in this view. */
@@ -110,38 +171,102 @@
return mBubble;
}
- /** Shows the app badge on this bubble. */
- void showBadge() {
+ void updateDotVisibility(boolean animate) {
+ final float targetScale = shouldDrawDot() ? 1f : 0f;
+ if (animate) {
+ animateDotScale();
+ } else {
+ mDotScale = targetScale;
+ mAnimatingToDotScale = targetScale;
+ invalidate();
+ }
+ }
+
+ void updateBadgeVisibility() {
if (mBubble instanceof BubbleBarOverflow) {
// The overflow bubble does not have a badge, so just bail.
return;
}
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
-
Bitmap appBadgeBitmap = bubble.getBadge();
- if (appBadgeBitmap == null) {
- mAppIcon.setVisibility(GONE);
+ int translationX = mOnLeft
+ ? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
+ : 0;
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
+ }
+
+ /** 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();
+ }
+
+ /** Whether this bubble is in the stack & not the first bubble. **/
+ boolean isBehindStack() {
+ return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
+ }
+
+ /** Whether the dot indicating unseen content in a bubble should be shown. */
+ private boolean shouldDrawDot() {
+ 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;
+ }
+
+ /** How big the dot should be, fraction from 0 to 1. */
+ private void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ /**
+ * 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;
}
- int translationX;
- if (mOnLeft) {
- translationX = -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
- } else {
- translationX = 0;
- }
+ mAnimatingToDotScale = toScale;
- mAppIcon.setTranslationX(translationX);
- mAppIcon.setVisibility(VISIBLE);
+ final boolean showDot = toScale > 0f;
+
+ // Do NOT wait until after animation ends to setShowDot
+ // to avoid overriding more recent showDot states.
+ clearAnimation();
+ animate()
+ .setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1f - fraction;
+ setDotScale(fraction);
+ }).withEndAction(() -> {
+ setDotScale(showDot ? 1f : 0f);
+ mDotIsAnimating = false;
+ }).start();
}
- /** Hides the app badge on this bubble. */
- void hideBadge() {
- mAppIcon.setVisibility(GONE);
- }
@Override
public String toString() {
- return "BubbleView{" + mBubble + "}";
+ String toString = mBubble != null ? mBubble.getKey() : "null";
+ return "BubbleView{" + toString + "}";
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 9fcadea..d78ca88 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -111,7 +111,7 @@
&& !toState.overviewUi;
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
- .animateAwayPlaceholder(mLauncher));
+ .createPlaceholderDismissAnim(mLauncher));
setter.setViewAlpha(
mRecentsView.getSplitInstructionsView(),
0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 3b53e8a..7ce87a3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -549,7 +549,7 @@
list.add(getDragController());
Consumer<AnimatorSet> splitAnimator = animatorSet -> {
AnimatorSet anim = mSplitSelectStateController.getSplitAnimationController()
- .animateAwayPlaceholder(QuickstepLauncher.this);
+ .createPlaceholderDismissAnim(QuickstepLauncher.this);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -1000,6 +1000,13 @@
return mSplitToWorkspaceController;
}
+ @Override
+ protected void handleSplitAnimationGoingToHome() {
+ super.handleSplitAnimationGoingToHome();
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(this);
+ }
+
public <T extends OverviewActionsView> T getActionsView() {
return (T) mActionsView;
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 7336718..3af9d5c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -646,13 +646,15 @@
/**
* Tells SysUI to show the bubble with the provided key.
* @param key the key of the bubble to show.
- * @param bubbleBarXCoordinate the X coordinate of the bubble bar on the screen.
- * @param bubbleBarYCoordinate the Y coordinate of the bubble bar on the screen.
+ * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
+ * axis.
+ * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
+ * axis.
*/
- public void showBubble(String key, int bubbleBarXCoordinate, int bubbleBarYCoordinate) {
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
if (mBubbles != null) {
try {
- mBubbles.showBubble(key, bubbleBarXCoordinate, bubbleBarYCoordinate);
+ mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showBubble");
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 2e8af4c..5740991 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -17,6 +17,8 @@
package com.android.quickstep.util
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.Bitmap
@@ -185,17 +187,32 @@
}
}
+ /** Does not play any animation if user is not currently in split selection state. */
+ fun playPlaceholderDismissAnim(launcher: Launcher) {
+ if (!splitSelectStateController.isSplitSelectActive) {
+ return
+ }
- fun animateAwayPlaceholder(mLauncher: Launcher) : AnimatorSet {
+ val anim = createPlaceholderDismissAnim(launcher)
+ anim.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ splitSelectStateController.resetState()
+ }
+ })
+ anim.start()
+ }
+
+ /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */
+ fun createPlaceholderDismissAnim(launcher: Launcher) : AnimatorSet {
val animatorSet = AnimatorSet()
- val recentsView : RecentsView<*, *> = mLauncher.getOverviewPanel()
+ val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
?: return animatorSet
// We are in split selection state currently, transitioning to another state
- val dragLayer: DragLayer = mLauncher.dragLayer
+ val dragLayer: DragLayer = launcher.dragLayer
val onScreenRectF = RectF()
- Utilities.getBoundsForViewInDragLayer(mLauncher.dragLayer, floatingTask,
+ Utilities.getBoundsForViewInDragLayer(launcher.dragLayer, floatingTask,
Rect(0, 0, floatingTask.width, floatingTask.height),
false, null, onScreenRectF)
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
@@ -214,7 +231,7 @@
floatingTask,
onScreenRectF,
floatingTask.stagePosition,
- mLauncher.deviceProfile
+ launcher.deviceProfile
)))
return animatorSet
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3ee9009..828d466 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -86,6 +86,10 @@
StateManager stateManager = mActivity.getStateManager();
animated &= stateManager.shouldAnimateStateChange();
stateManager.goToState(NORMAL, animated);
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(mActivity);
+ }
AbstractFloatingView.closeAllOpenViews(mActivity, animated);
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 849db28..9efab36 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -424,6 +424,7 @@
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 1");
mStartupLatencyLogger = createStartupLatencyLogger(
sIsNewProcess
? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
@@ -582,6 +583,7 @@
}
setTitle(R.string.home_screen);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 2");
}
/**
@@ -1056,6 +1058,7 @@
@Override
protected void onStop() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 1");
super.onStop();
if (mDeferOverlayCallbacks) {
checkIfOverlayStillDeferred();
@@ -1067,10 +1070,12 @@
mAppWidgetHolder.setActivityStarted(false);
NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
FloatingIconView.resetIconLoadResult();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 2");
}
@Override
protected void onStart() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 1");
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
super.onStart();
if (!mDeferOverlayCallbacks) {
@@ -1079,6 +1084,7 @@
mAppWidgetHolder.setActivityStarted(true);
TraceHelper.INSTANCE.endSection();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 2");
}
@Override
@@ -1249,6 +1255,7 @@
@Override
protected void onResume() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 1");
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
@@ -1260,10 +1267,12 @@
DragView.removeAllViews(this);
TraceHelper.INSTANCE.endSection();
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 2");
}
@Override
protected void onPause() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 1");
// Ensure that items added to Launcher are queued until Launcher returns
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
@@ -1276,6 +1285,7 @@
mOverlayManager.onActivityPaused(this);
}
mAppWidgetHolder.setActivityResumed(false);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 2");
}
/**
@@ -1683,6 +1693,9 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ handleSplitAnimationGoingToHome();
+ }
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
@@ -1696,6 +1709,11 @@
TraceHelper.INSTANCE.endSection();
}
+ /** Handle animating away split placeholder view when user taps on home button */
+ protected void handleSplitAnimationGoingToHome() {
+ // Overridden
+ }
+
protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
@@ -1740,6 +1758,8 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 1");
outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
mWorkspace.getCurrentPageScreenIds().getArray().toArray());
outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
@@ -1771,10 +1791,13 @@
super.onSaveInstanceState(outState);
mOverlayManager.onActivitySaveInstanceState(this, outState);
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 2");
}
@Override
public void onDestroy() {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 1");
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
@@ -1797,6 +1820,7 @@
LauncherAppState.getIDP(this).removeOnChangeListener(this);
mOverlayManager.onActivityDestroyed(this);
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 2");
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -2961,7 +2985,7 @@
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
boolean hadWorkApps = mAppsView.shouldShowTabs();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
if (hadWorkApps != mAppsView.shouldShowTabs()) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index da1bcd7..9b7a05f 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -139,7 +139,7 @@
private final SearchTransitionController mSearchTransitionController;
private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect mInsets = new Rect();
- private final AllAppsStore mAllAppsStore;
+ private final AllAppsStore<T> mAllAppsStore;
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -192,7 +192,7 @@
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivityContext = ActivityContext.lookupContext(context);
- mAllAppsStore = new AllAppsStore(mActivityContext);
+ mAllAppsStore = new AllAppsStore<>(mActivityContext);
mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mHeaderThreshold = getResources().getDimensionPixelSize(
@@ -892,7 +892,7 @@
container.put(R.id.work_tab_state_id, state);
}
- public AllAppsStore getAppsStore() {
+ public AllAppsStore<T> getAppsStore() {
return mAllAppsStore;
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 29767bf..0657178 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -69,7 +69,7 @@
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@Nullable
- private final AllAppsStore mAllAppsStore;
+ private final AllAppsStore<T> mAllAppsStore;
// The number of results in current adapter
private int mAccessibilityResultsCount = 0;
@@ -86,7 +86,7 @@
private int mNumAppRowsInAdapter;
private Predicate<ItemInfo> mItemFilter;
- public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
+ public AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore,
WorkProfileManager workProfileManager) {
mAllAppsStore = appsStore;
mActivityContext = ActivityContext.lookupContext(context);
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index a572a60..66001d8 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -32,6 +32,7 @@
import android.view.View;
import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
@@ -87,6 +88,7 @@
private final View mRoot;
private final BaseDraggingActivity mActivity;
private final boolean mHideSysUiScrim;
+ private boolean mSkipScrimAnimationForTest = false;
private boolean mAnimateScrimOnNextDraw = false;
private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha);
@@ -189,6 +191,15 @@
mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h);
}
+ /**
+ * Sets whether the SysUiScrim should hide for testing.
+ */
+ @VisibleForTesting
+ public void skipScrimAnimation() {
+ mSkipScrimAnimationForTest = true;
+ reapplySysUiAlpha();
+ }
+
private void reapplySysUiAlpha() {
reapplySysUiAlphaNoInvalidate();
if (!mHideSysUiScrim) {
@@ -198,6 +209,7 @@
private void reapplySysUiAlphaNoInvalidate() {
float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value;
+ if (mSkipScrimAnimationForTest) factor = 1f;
mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
}
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index f03c62a..2d69bfa 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -60,13 +60,15 @@
private final OnClickListener mOnClickListener;
private final OnLongClickListener mOnLongClickListener;
private final SharedPreferences mPrefs;
- private final AllAppsStore mAllAppsList;
+ private final AllAppsStore<SecondaryDisplayLauncher> mAllAppsList;
private final AppInfoComparator mAppNameComparator;
private final Set<ComponentKey> mPinnedApps = new HashSet<>();
private final ArrayList<AppInfo> mItems = new ArrayList<>();
- public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ public PinnedAppsAdapter(
+ SecondaryDisplayLauncher launcher,
+ AllAppsStore<SecondaryDisplayLauncher> allAppsStore,
OnLongClickListener onLongClickListener) {
mLauncher = launcher;
mOnClickListener = launcher.getItemOnClickListener();
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index e4f6fe1..a10c0ad 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -302,7 +302,7 @@
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 4073517..75cee2f 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -157,6 +157,7 @@
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
public static final String ICON_MISSING = "b/282963545";
+ public static final String ACTIVITY_LIFECYCLE_RULE = "b/289161193";
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 435649b..b05ebf8 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.PlatinumTest;
+import android.platform.test.rule.ScreenRecordRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -53,7 +54,9 @@
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/289161193
public void testDragIcon() throws Throwable {
+ mLauncher.enableDebugTracing(); // b/289161193
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -79,6 +82,7 @@
DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
+ mLauncher.disableDebugTracing(); // b/289161193
}
/**
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
index 2eedec3..b5d8193 100644
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -22,6 +22,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.launcher3.testing.shared.TestProtocol;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -71,33 +73,57 @@
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
if (activity != null && mClass.isInstance(activity)) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityCreated");
mActivity = (T) activity;
}
}
@Override
public void onActivityStarted(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityStarted");
+ }
}
@Override
public void onActivityResumed(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityResumed");
+ }
}
@Override
public void onActivityPaused(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityPaused");
+ }
}
@Override
public void onActivityStopped(Activity activity) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onAcgtivityStopped");
+ }
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ if (activity == mActivity) {
+ TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE,
+ "MyStatement.onActivitySaveInstanceState");
+ }
}
@Override
public void onActivityDestroyed(Activity activity) {
if (activity == mActivity) {
+ TestProtocol.testLogD(
+ TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityDestroyed");
mActivity = null;
}
}