From 596688d10f48602410ecf5f9076f51bd409b38ea Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 15 Feb 2023 17:17:29 -0800 Subject: [PATCH] Introduce BubbleBarLayerView & animation for BubbleBarExpandedView * BubbleBarLayerView will be used in place of BubbleStackView when the bubbles are shown in the bubble bar in launcher. It'll be added to the window manager instead and it'll host and animate the expanded view for bubbles from the bubble bar. * BubbleBarExpandedViewAnimationHelper contains code to animate the expanded view shown in BubbleBarLayerView. * This CL just adds the classes, usuage of them happens in a future CL. Test: treehugger / manual with other CLs Bug: 253318833 Change-Id: I79ab3cbe357439f1ec51d48c5f06504b899509f0 --- .../wm/shell/bubbles/BubbleController.java | 2 +- .../bubbles/bar/BubbleBarAnimationHelper.java | 233 ++++++++++++++++++ .../shell/bubbles/bar/BubbleBarLayerView.java | 212 ++++++++++++++++ 3 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 3dbb745f0c6c7..8040bd72e2a3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -682,7 +682,7 @@ public class BubbleController implements ConfigurationChangeListener, return mBubblePositioner; } - Bubbles.SysuiProxy getSysuiProxy() { + public Bubbles.SysuiProxy getSysuiProxy() { return mSysuiProxy; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java new file mode 100644 index 0000000000000..23f65f943aa46 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.bar; + +import static android.view.View.VISIBLE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.PointF; +import android.util.Log; +import android.widget.FrameLayout; + +import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleViewProvider; +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; + +/** + * Helper class to animate a {@link BubbleBarExpandedView} on a bubble. + */ +public class BubbleBarAnimationHelper { + + private static final String TAG = BubbleBarAnimationHelper.class.getSimpleName(); + + private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; + private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; + private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; + + /** Spring config for the expanded view scale-in animation. */ + private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = + new PhysicsAnimator.SpringConfig(300f, 0.9f); + + /** Spring config for the expanded view scale-out animation. */ + private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = + new PhysicsAnimator.SpringConfig(900f, 1f); + + /** Matrix used to scale the expanded view container with a given pivot point. */ + private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); + + /** Animator for animating the expanded view's alpha (including the TaskView inside it). */ + private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f); + + private final Context mContext; + private final BubbleBarLayerView mLayerView; + private final BubblePositioner mPositioner; + + private BubbleViewProvider mExpandedBubble; + private boolean mIsExpanded = false; + + public BubbleBarAnimationHelper(Context context, + BubbleBarLayerView bubbleBarLayerView, + BubblePositioner positioner) { + mContext = context; + mLayerView = bubbleBarLayerView; + mPositioner = positioner; + + mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION); + mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + // We need to be Z ordered on top in order for alpha animations to work. + mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true); + mExpandedBubble.getBubbleBarExpandedView().setAnimating(true); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + // The surface needs to be Z ordered on top for alpha values to work on the + // TaskView, and if we're temporarily hidden, we are still on the screen + // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha + // = 0f remains in effect. + if (mIsExpanded) { + mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false); + } + + mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded); + mExpandedBubble.getBubbleBarExpandedView().setAnimating(false); + } + } + }); + mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + float alpha = (float) valueAnimator.getAnimatedValue(); + mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha); + mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha); + } + }); + } + + /** + * Animates the provided bubble's expanded view to the expanded state. + */ + public void animateExpansion(BubbleViewProvider expandedBubble) { + mExpandedBubble = expandedBubble; + if (mExpandedBubble == null) { + return; + } + BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView(); + if (bev == null) { + return; + } + mIsExpanded = true; + + mExpandedViewContainerMatrix.setScaleX(0f); + mExpandedViewContainerMatrix.setScaleY(0f); + + updateExpandedView(); + bev.setAnimating(true); + bev.setContentVisibility(false); + bev.setAlpha(0f); + bev.setTaskViewAlpha(0f); + bev.setVisibility(VISIBLE); + + // Set the pivot point for the scale, so the view animates out from the bubble bar. + PointF bubbleBarPosition = mPositioner.getBubbleBarPosition(); + mExpandedViewContainerMatrix.setScale( + 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, + 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, + bubbleBarPosition.x, + bubbleBarPosition.y); + + bev.setAnimationMatrix(mExpandedViewContainerMatrix); + + mExpandedViewAlphaAnimator.start(); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( + mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + bev.setAnimationMatrix(null); + updateExpandedView(); + bev.setSurfaceZOrderedOnTop(false); + }) + .start(); + } + + /** + * Collapses the currently expanded bubble. + * + * @param endRunnable a runnable to run at the end of the animation. + */ + public void animateCollapse(Runnable endRunnable) { + mIsExpanded = false; + if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + Log.w(TAG, "Trying to animate collapse without a bubble"); + return; + } + + mExpandedViewContainerMatrix.setScaleX(1f); + mExpandedViewContainerMatrix.setScaleY(1f); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor( + EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), + mScaleOutSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor( + EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), + mScaleOutSpringConfig) + .addUpdateListener((target, values) -> { + if (mExpandedBubble != null + && mExpandedBubble.getBubbleBarExpandedView() != null) { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( + mExpandedViewContainerMatrix); + } + }) + .withEndActions(() -> { + if (mExpandedBubble != null + && mExpandedBubble.getBubbleBarExpandedView() != null) { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null); + } + if (endRunnable != null) { + endRunnable.run(); + } + }) + .start(); + mExpandedViewAlphaAnimator.reverse(); + } + + private void updateExpandedView() { + if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + Log.w(TAG, "Trying to update the expanded view without a bubble"); + return; + } + BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + + final int padding = mPositioner.getBubbleBarExpandedViewPadding(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); + lp.width = width; + lp.height = height; + bbev.setLayoutParams(lp); + if (mLayerView.isOnLeft()) { + bbev.setX(mPositioner.getInsets().left + padding); + } else { + bbev.setX(mPositioner.getAvailableRect().width() - width - padding); + } + bbev.setY(mPositioner.getInsets().top + padding); + bbev.updateLocation(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java new file mode 100644 index 0000000000000..b1a725b6e5c4b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles.bar; + +import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; +import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.ColorDrawable; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; + +import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleViewProvider; + +/** + * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window + * manager to display bubbles. However, it is only used when bubbles are being displayed in + * launcher in the bubble bar. This view does not show a stack of bubbles that can be moved around + * on screen and instead shows & animates the expanded bubble for the bubble bar. + */ +public class BubbleBarLayerView extends FrameLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { + + private static final String TAG = BubbleBarLayerView.class.getSimpleName(); + + private static final float SCRIM_ALPHA = 0.2f; + + private final BubbleController mBubbleController; + private final BubblePositioner mPositioner; + private final BubbleBarAnimationHelper mAnimationHelper; + private final View mScrimView; + + @Nullable + private BubbleViewProvider mExpandedBubble; + private BubbleBarExpandedView mExpandedView; + + // TODO(b/273310265) - currently the view is always on the right, need to update for RTL. + /** Whether the expanded view is displaying on the left of the screen or not. */ + private boolean mOnLeft = false; + + /** Whether a bubble is expanded. */ + private boolean mIsExpanded = false; + + private final Region mTouchableRegion = new Region(); + private final Rect mTempRect = new Rect(); + + public BubbleBarLayerView(Context context, BubbleController controller) { + super(context); + mBubbleController = controller; + mPositioner = mBubbleController.getPositioner(); + + mAnimationHelper = new BubbleBarAnimationHelper(context, + this, mPositioner); + + mScrimView = new View(getContext()); + mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mScrimView.setBackgroundDrawable(new ColorDrawable( + getResources().getColor(android.R.color.system_neutral1_1000))); + addView(mScrimView); + mScrimView.setAlpha(0f); + mScrimView.setBackgroundDrawable(new ColorDrawable( + getResources().getColor(android.R.color.system_neutral1_1000))); + + setOnClickListener(view -> { + mBubbleController.collapseStack(); + }); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mPositioner.update(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + + if (mExpandedView != null) { + removeView(mExpandedView); + mExpandedView = null; + } + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + mTouchableRegion.setEmpty(); + getTouchableRegion(mTouchableRegion); + inoutInfo.touchableRegion.set(mTouchableRegion); + } + + /** Updates the sizes of any displaying expanded view. */ + public void onDisplaySizeChanged() { + if (mIsExpanded && mExpandedView != null) { + updateExpandedView(); + } + } + + /** Whether the stack of bubbles is expanded or not. */ + public boolean isExpanded() { + return mIsExpanded; + } + + // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done. + /** Whether the expanded view is positioned on the left or right side of the screen. */ + public boolean isOnLeft() { + return mOnLeft; + } + + /** Shows the expanded view of the provided bubble. */ + public void showExpandedView(BubbleViewProvider b) { + BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); + if (expandedView == null) { + return; + } + if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { + removeView(mExpandedView); + mExpandedView = null; + } + if (mExpandedView == null) { + mExpandedBubble = b; + mExpandedView = expandedView; + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + mExpandedView.setVisibility(GONE); + addView(mExpandedView, new FrameLayout.LayoutParams(width, height)); + } + + mIsExpanded = true; + mBubbleController.getSysuiProxy().onStackExpandChanged(true); + mAnimationHelper.animateExpansion(mExpandedBubble); + showScrim(true); + } + + /** Collapses any showing expanded view */ + public void collapse() { + mIsExpanded = false; + final BubbleBarExpandedView viewToRemove = mExpandedView; + mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); + mBubbleController.getSysuiProxy().onStackExpandChanged(false); + mExpandedView = null; + showScrim(false); + } + + /** Updates the expanded view size and position. */ + private void updateExpandedView() { + if (mExpandedView == null) return; + final int padding = mPositioner.getBubbleBarExpandedViewPadding(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams(); + lp.width = width; + lp.height = height; + mExpandedView.setLayoutParams(lp); + if (mOnLeft) { + mExpandedView.setX(mPositioner.getInsets().left + padding); + } else { + mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding); + } + mExpandedView.setY(mPositioner.getInsets().top + padding); + mExpandedView.updateLocation(); + } + + private void showScrim(boolean show) { + if (show) { + mScrimView.animate() + .setInterpolator(ALPHA_IN) + .alpha(SCRIM_ALPHA) + .start(); + } else { + mScrimView.animate() + .alpha(0f) + .setInterpolator(ALPHA_OUT) + .start(); + } + } + + /** + * Fills in the touchable region for expanded view. This is used by window manager to + * decide which touch events go to the expanded view. + */ + private void getTouchableRegion(Region outRegion) { + mTempRect.setEmpty(); + if (mIsExpanded) { + getBoundsOnScreen(mTempRect); + outRegion.op(mTempRect, Region.Op.UNION); + } + } +}