Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 074a3a66 authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Initial animation of new bubble bar bubble

This change animates a new bubble bar bubble. Currently only handles the case where the bubble is added when the bubble bar is stashed.
There's no synchronization with the stash handle yet either.

Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT
Bug: 280605790
Test: atest BubbleBarViewAnimatorTest
Change-Id: Ic3f5edc0dde204f871cf7e1288bd50620e6beebb
parent 65bf9fac
Loading
Loading
Loading
Loading
+3 −3
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@
 */
 */
package com.android.launcher3.taskbar.bubbles
package com.android.launcher3.taskbar.bubbles


import android.content.Context
import android.graphics.Canvas
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.ColorFilter
@@ -27,12 +28,10 @@ import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.wm.shell.common.TriangleShape
import com.android.wm.shell.common.TriangleShape


/** Drawable for the background of the bubble bar. */
/** Drawable for the background of the bubble bar. */
class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
class BubbleBarBackground(context: Context, private val backgroundHeight: Float) : Drawable() {
    Drawable() {


    private val DARK_THEME_SHADOW_ALPHA = 51f
    private val DARK_THEME_SHADOW_ALPHA = 51f
    private val LIGHT_THEME_SHADOW_ALPHA = 25f
    private val LIGHT_THEME_SHADOW_ALPHA = 25f
@@ -46,6 +45,7 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun


    var arrowPositionX: Float = 0f
    var arrowPositionX: Float = 0f
        private set
        private set

    private var showingArrow: Boolean = false
    private var showingArrow: Boolean = false
    private var arrowDrawable: ShapeDrawable
    private var arrowDrawable: ShapeDrawable


+31 −5
Original line number Original line Diff line number Diff line
@@ -39,8 +39,6 @@ import androidx.dynamicanimation.animation.SpringForce;


import com.android.launcher3.R;
import com.android.launcher3.R;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.views.ActivityContext;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;


import java.util.List;
import java.util.List;
@@ -159,8 +157,6 @@ public class BubbleBarView extends FrameLayout {


    public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        super(context, attrs, defStyleAttr, defStyleRes);
        TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);

        setAlpha(0);
        setAlpha(0);
        setVisibility(INVISIBLE);
        setVisibility(INVISIBLE);
        mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
        mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
@@ -171,7 +167,7 @@ public class BubbleBarView extends FrameLayout {


        setClipToPadding(false);
        setClipToPadding(false);


        mBubbleBarBackground = new BubbleBarBackground(activityContext,
        mBubbleBarBackground = new BubbleBarBackground(context,
                getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
                getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
        setBackgroundDrawable(mBubbleBarBackground);
        setBackgroundDrawable(mBubbleBarBackground);


@@ -379,6 +375,36 @@ public class BubbleBarView extends FrameLayout {
        return mRelativePivotY;
        return mRelativePivotY;
    }
    }


    /** Prepares for animating a bubble while being stashed. */
    public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) {
        // we're about to animate the new bubble in. the new bubble has already been added to this
        // view, but we're currently stashed, so before we can start the animation we need make
        // everything else in the bubble bar invisible, except for the bubble that's being animated.
        setBackground(null);
        for (int i = 0; i < getChildCount(); i++) {
            final BubbleView view = (BubbleView) getChildAt(i);
            final String key = view.getBubble().getKey();
            if (!bubbleKey.equals(key)) {
                view.setVisibility(INVISIBLE);
            }
        }
        setVisibility(VISIBLE);
        setAlpha(1);
        setTranslationY(0);
        setScaleX(1);
        setScaleY(1);
    }

    /** Resets the state after the bubble animation completed. */
    public void onAnimatingBubbleCompleted() {
        setBackground(mBubbleBarBackground);
        for (int i = 0; i < getChildCount(); i++) {
            final BubbleView view = (BubbleView) getChildAt(i);
            view.setVisibility(VISIBLE);
            view.setAlpha(1f);
        }
    }

    // TODO: (b/280605790) animate it
    // TODO: (b/280605790) animate it
    @Override
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+11 −0
Original line number Original line Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.SystemUiProxy;
@@ -81,6 +82,8 @@ public class BubbleBarViewController {
    private boolean mHiddenForNoBubbles = true;
    private boolean mHiddenForNoBubbles = true;
    private boolean mShouldShowEducation;
    private boolean mShouldShowEducation;


    private BubbleBarViewAnimator mBubbleBarViewAnimator;

    public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
    public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
        mActivity = activity;
        mActivity = activity;
        mBarView = barView;
        mBarView = barView;
@@ -113,6 +116,8 @@ public class BubbleBarViewController {
        mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
        mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
                mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
                mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
        );
        );

        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
    }
    }


    private void onBubbleClicked(View v) {
    private void onBubbleClicked(View v) {
@@ -316,6 +321,12 @@ public class BubbleBarViewController {
                    new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
                    new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
            b.getView().setOnClickListener(mBubbleClickListener);
            b.getView().setOnClickListener(mBubbleClickListener);
            mBubbleDragController.setupBubbleView(b.getView());
            mBubbleDragController.setupBubbleView(b.getView());

            boolean isStashedOrGone =
                    mBubbleStashController.isStashed() || mBarView.getVisibility() != VISIBLE;
            if (b instanceof BubbleBarBubble && isStashedOrGone) {
                mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
            }
        } else {
        } else {
            Log.w(TAG, "addBubble, bubble was null!");
            Log.w(TAG, "addBubble, bubble was null!");
        }
        }
+3 −3
Original line number Original line Diff line number Diff line
@@ -145,7 +145,7 @@ public class BubbleView extends ConstraintLayout {
    }
    }


    /** Sets the bubble being rendered in this view. */
    /** Sets the bubble being rendered in this view. */
    void setBubble(BubbleBarBubble bubble) {
    public void setBubble(BubbleBarBubble bubble) {
        mBubble = bubble;
        mBubble = bubble;
        mBubbleIcon.setImageBitmap(bubble.getIcon());
        mBubbleIcon.setImageBitmap(bubble.getIcon());
        mAppIcon.setImageBitmap(bubble.getBadge());
        mAppIcon.setImageBitmap(bubble.getBadge());
@@ -159,7 +159,7 @@ public class BubbleView extends ConstraintLayout {
     * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
     * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
     * come from an app.
     * come from an app.
     */
     */
    void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
    public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
        mBubble = overflow;
        mBubble = overflow;
        mBubbleIcon.setImageBitmap(bitmap);
        mBubbleIcon.setImageBitmap(bitmap);
        mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
        mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
@@ -168,7 +168,7 @@ public class BubbleView extends ConstraintLayout {


    /** Returns the bubble being rendered in this view. */
    /** Returns the bubble being rendered in this view. */
    @Nullable
    @Nullable
    BubbleBarItem getBubble() {
    public BubbleBarItem getBubble() {
        return mBubble;
        return mBubble;
    }
    }


+118 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.taskbar.bubbles.animation

import android.view.View
import android.view.View.VISIBLE
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleStashController
import com.android.launcher3.taskbar.bubbles.BubbleView
import com.android.wm.shell.shared.animation.PhysicsAnimator

/** Handles animations for bubble bar bubbles. */
class BubbleBarViewAnimator
@JvmOverloads
constructor(
    private val bubbleBarView: BubbleBarView,
    private val bubbleStashController: BubbleStashController,
    private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
) {

    private companion object {
        /** The time to show the flyout. */
        const val FLYOUT_DELAY_MS: Long = 2500
        /** The translation Y the new bubble will animate to. */
        const val BUBBLE_ANIMATION_TRANSLATION_Y = -50f
    }

    /** An interface for scheduling jobs. */
    interface Scheduler {

        /** Schedule the given [block] to run. */
        fun post(block: () -> Unit)

        /** Schedule the given [block] to start with a delay of [delayMillis]. */
        fun postDelayed(delayMillis: Long, block: () -> Unit)
    }

    /** A [Scheduler] that uses a Handler to run jobs. */
    private class HandlerScheduler(private val view: View) : Scheduler {

        override fun post(block: () -> Unit) {
            view.post(block)
        }

        override fun postDelayed(delayMillis: Long, block: () -> Unit) {
            view.postDelayed(block, delayMillis)
        }
    }

    private val springConfig =
        PhysicsAnimator.SpringConfig(
            stiffness = SpringForce.STIFFNESS_LOW,
            dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
        )

    /** Animates a bubble for the state where the bubble bar is stashed. */
    fun animateBubbleInForStashed(b: BubbleBarBubble) {
        val bubbleView = b.view
        val animator = PhysicsAnimator.getInstance(bubbleView)
        if (animator.isRunning()) animator.cancel()
        // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
        // and the second part hides it after a delay.
        val showAnimation = buildShowAnimation(bubbleView, b.key, animator)
        val hideAnimation = buildHideAnimation(animator)
        scheduler.post(showAnimation)
        scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
    }

    /** Returns a lambda that starts the animation that shows the new bubble. */
    private fun buildShowAnimation(
        bubbleView: BubbleView,
        key: String,
        animator: PhysicsAnimator<BubbleView>
    ): () -> Unit = {
        bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
        animator.setDefaultSpringConfig(springConfig)
        animator
            .spring(DynamicAnimation.ALPHA, 1f)
            .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y)
        bubbleView.alpha = 0f
        bubbleView.visibility = VISIBLE
        animator.start()
    }

    /** Returns a lambda that starts the animation that hides the new bubble. */
    private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = {
        animator.setDefaultSpringConfig(springConfig)
        animator
            .spring(DynamicAnimation.ALPHA, 0f)
            .spring(DynamicAnimation.TRANSLATION_Y, 0f)
            .addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded ->
                if (!canceled && allRelevantPropertyAnimsEnded) {
                    if (bubbleStashController.isStashed) {
                        bubbleBarView.alpha = 0f
                    }
                    bubbleBarView.onAnimatingBubbleCompleted()
                }
            }
        animator.start()
    }
}
Loading