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

Commit 1158a084 authored by Mady Mellor's avatar Mady Mellor
Browse files

Add & implement interface for BubbleTransitions to animate bubble views

Currently, BubbleTransitions only considers BubbleBarLayerView. This
means there are some issues in floating mode where BubbleStackView is
used. Namely, if a bubble'd task starts a new task, it won't get
converted into a bubble & bubbles that have a task trampoline in their
launch won't stay in the bubble.

This CL creates an interface for BubbleTransitions to communicate
with the expanded view parents (layerView & stackView). This is
the quickest/least invasive way to resolve these issues.

There's not a whole lot BubbleStackView needs to do for the interface as
we are not supporting fullscreen transitions right now, but that
will be something we need to handle in the future. At that point in
time, it might be worth considering extracting out a common animator
that can animate BubbleExpandedView/BubbleBarExpandedView (or even
try to have a common expanded view) and have one of those implement
this  interface instead.

Flag: com.android.wm.shell.enable_create_any_bubble
Bug: 408453889
Test: atest BubbleStackViewTest BubbletransitionsTest DefaultMixedHandlerTest
Test: manual - create a gmail bubble on phone, tap compose
             => observe that a 2nd gmail bubble is created
             => verify that both bubbles work after expand/collapse
                /switch
             - create a google tv bubble (does a task trampoline)
             => verify that google tv ends up in a bubble (note you do
                see a bit of an animation of the first task bubble added
                and then removed as well)
Change-Id: I9caf8bf68db035fd6d6ea429446ff88538030393
parent 813c9541
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
@@ -711,6 +711,112 @@ class BubbleStackViewTest {
        verify(bubbleStackView).stopMonitoringSwipeUpGesture()
    }

    @Test
    fun animateExpand_expandRunsRunnable() {
        bubbleStackView = spy(bubbleStackView)
        val bubble = createAndInflateChatBubble(key = "bubble")

        assertThat(bubble.expandedView).isNotNull()

        var afterTransitionRan = false
        val semaphore = Semaphore(0)

        // Expand animation runs on a delay so wait for it.
        val runnable = Runnable {
            afterTransitionRan = true
            semaphore.release()
         }

        assertThat(bubbleStackView.isExpanded).isFalse()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
            bubbleStackView.setSelectedBubble(bubble)
            bubbleStackView.animateExpand(null, runnable)
            bubbleStackView.isExpanded = true
            shellExecutor.flushAll()
        }

        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(bubbleStackView.isExpanded).isTrue()
        assertThat(afterTransitionRan).isTrue()
    }

    @Test
    fun animateExpand_switchRunsRunnable() {
        bubbleStackView = spy(bubbleStackView)
        val bubble = createAndInflateChatBubble(key = "bubble")
        val bubble2 = createAndInflateChatBubble(key = "bubble2")

        var afterTransitionRan = false
        val semaphore = Semaphore(0)

        // Expand animation runs on a delay so wait for it.
        val runnable = Runnable {
            afterTransitionRan = true
            semaphore.release()
        }
        assertThat(bubbleStackView.isExpanded).isFalse()

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
            bubbleStackView.addBubble(bubble2)
            bubbleStackView.setSelectedBubble(bubble)
            bubbleStackView.isExpanded = true
            shellExecutor.flushAll()
        }

        assertThat(bubbleStackView.isExpanded).isTrue()
        assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble.key)

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.animateExpand(null, runnable)
            bubbleStackView.setSelectedBubble(bubble2)
            shellExecutor.flushAll()
        }

        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
        assertThat(bubbleStackView.isExpanded).isTrue()
        assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble2.key)
        assertThat(afterTransitionRan).isTrue()
    }

    @Test
    fun canExpandView_true_triggersContinueExpand() {
        bubbleStackView = spy(bubbleStackView)
        val bubble = createAndInflateChatBubble(key = "bubble")
        val bubbleTransition = mock<BubbleTransitions.BubbleTransition>()
        bubble.preparingTransition = bubbleTransition

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
        }

        assertThat(bubbleStackView.isExpanded).isFalse()
        assertThat(bubbleStackView.canExpandView(bubble)).isTrue()
        verify(bubbleTransition).continueExpand()
    }

    @Test
    fun canExpandView_false() {
        bubbleStackView = spy(bubbleStackView)
        val bubble = createAndInflateChatBubble(key = "bubble")
        val bubbleTransition = mock<BubbleTransitions.BubbleTransition>()
        bubble.preparingTransition = bubbleTransition

        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            bubbleStackView.addBubble(bubble)
            bubbleStackView.setSelectedBubble(bubble)
            bubbleStackView.isExpanded = true
            shellExecutor.flushAll()
        }

        assertThat(bubbleStackView.isExpanded).isTrue()
        assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble.key)
        assertThat(bubbleStackView.canExpandView(bubble)).isFalse()
        verify(bubbleTransition, never()).continueExpand()
    }

    private fun createAndInflateChatBubble(key: String): Bubble {
        val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
        val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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;

import android.annotation.Nullable;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.View;

import androidx.annotation.NonNull;

import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;

/**
 * {@link BubbleTransitions} needs to perform various actions on the bubble expanded view and its
 * parent. There are two variants of the expanded view -- one belonging to {@link BubbleStackView}
 * and one belonging to {@link BubbleBarLayerView}. This interface is implemented by the view
 * parents to allow the transitions to modify and animate the expanded view.
 * <p>
 * TODO (b/349844986):
 * Ideally we could have a single type of expanded view (or view animator) used by both stackView
 * & layerView and then the transitions could perhaps operate on that directly.
 */
public interface BubbleExpandedViewTransitionAnimator {

    /**
     * Whether bubble UI is currently expanded.
     */
    boolean isExpanded();

    /**
     * Whether it's possible to expand {@param bubble} right now. This is {@code false} if the
     * bubble has no view or if the bubble is already showing.
     */
    boolean canExpandView(BubbleViewProvider bubble);

    /**
     * Call to prepare the provided {@param bubble} to be animated.
     * <p>
     * Should make the current expanded bubble visible immediately so it gets a surface that can be
     * animated. Since the surface may not be ready yet, it should keep the TaskView alpha=0.
     */
    BubbleViewProvider prepareConvertedView(BubbleViewProvider bubble);

    /**
     * Animates a visible task into the bubble expanded view.
     *
     * @param startT A transaction with first-frame work. This *must* be applied here!
     * @param startBounds The starting bounds of the task being converted into a bubble.
     * @param startScale The starting scale of the task being converted into a bubble.
     * @param snapshot A snapshot of the task being converted into a bubble.
     * @param taskLeash The taskLeash of the task being converted into a bubble.
     * @param animFinish A runnable to run at the end of the animation.
     */
    void animateConvert(@NonNull SurfaceControl.Transaction startT,
            @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot,
            SurfaceControl taskLeash, Runnable animFinish);

    /**
     * Animates a non-visible task into the bubble expanded view -- since there's no task
     * visible this just needs to expand the bubble stack (or animate out the previously
     * selected bubble if already expanded).
     *
     * @param previousBubble If non-null, this is a bubble that is already showing before the new
     *                       bubble is expanded.
     * @param animFinish If non-null, the callback triggered after the expand animation completes
     */
    void animateExpand(@Nullable BubbleViewProvider previousBubble, @Nullable Runnable animFinish);

    /**
     * Bubble transitions calls this when a view should be removed from the parent.
     */
    void removeViewFromTransition(View view);
}
+71 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES
import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT;
import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES_NOISY;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
@@ -52,6 +53,7 @@ import android.util.Log;
import android.view.Choreographer;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
@@ -114,7 +116,8 @@ import java.util.stream.Collectors;
 * Renders bubbles in a stack and handles animating expanded and collapsed states.
 */
public class BubbleStackView extends FrameLayout
        implements ViewTreeObserver.OnComputeInternalInsetsListener {
        implements ViewTreeObserver.OnComputeInternalInsetsListener,
        BubbleExpandedViewTransitionAnimator {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;

    /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
@@ -180,6 +183,12 @@ public class BubbleStackView extends FrameLayout
     */
    private final ShellExecutor mMainExecutor;
    private Runnable mDelayedAnimation;
    /**
     * Runnable set after a bubble is created via a transition; should run after expand or switch
     * animation is complete.
     */
    @Nullable
    private Runnable mAfterTransitionRunnable = null;

    /**
     * Interface to synchronize {@link View} state and the screen.
@@ -1181,6 +1190,58 @@ public class BubbleStackView extends FrameLayout
        });
    }

    @Override
    public boolean canExpandView(BubbleViewProvider b) {
        if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
            // Already showing this bubble so can't expand it.
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                    "BubbleStackView.canExpandView - false %s already expanded", b.getKey());
            return false;
        }
        if (b instanceof Bubble) {
            BubbleTransitions.BubbleTransition transition = ((Bubble) b).getPreparingTransition();
            if (transition != null) {
                // StackView doesn't need to wait for launcher to expand, if we're able to expand,
                // mark it as ready now.
                transition.continueExpand();
            }
        }
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleStackView.canExpandView - true");
        return true;
    }

    @Override
    public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) {
        // TODO b/419347947 - if we support converting visible tasks to bubbles in the future
        //  this might have to do some stuff.
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleStackView.prepareConvertedView - doing nothing");
        return b;
    }

    @Override
    public void animateConvert(@NonNull SurfaceControl.Transaction startT,
            @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot,
            SurfaceControl taskLeash, Runnable animFinish) {
        // TODO b/419347947 - if we support converting visible tasks to bubbles in the future
        //  this will have to do some stuff.
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleStackView.animateConvert - doing nothing");
    }

    @Override
    public void animateExpand(@Nullable BubbleViewProvider previousBubble,
            @Nullable Runnable animFinish) {
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleStackView.animateExpand -- caching runnable");
        mAfterTransitionRunnable = animFinish;
    }

    @Override
    public void removeViewFromTransition(View view) {
        // TODO b/419347947 - if we support converting visible tasks to bubbles in the future
        //  this will have to do some stuff.
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                "BubbleStackView.removeViewFromTransition - doing nothing");
    }

    /**
     * Reset state related to dragging.
     */
@@ -1967,6 +2028,7 @@ public class BubbleStackView extends FrameLayout
    /**
     * Whether the stack of bubbles is expanded or not.
     */
    @Override
    public boolean isExpanded() {
        return mIsExpanded;
    }
@@ -2688,6 +2750,10 @@ public class BubbleStackView extends FrameLayout
                        if (expView != null) {
                            expView.setSurfaceZOrderedOnTop(false);
                        }
                        if (mAfterTransitionRunnable != null) {
                            mAfterTransitionRunnable.run();
                            mAfterTransitionRunnable = null;
                        }
                    })
                    .start();
        };
@@ -2900,6 +2966,10 @@ public class BubbleStackView extends FrameLayout
                            expandedView.setSurfaceZOrderedOnTop(false);
                            expandedView.setAnimating(false);
                        }
                        if (mAfterTransitionRunnable != null) {
                            mAfterTransitionRunnable.run();
                            mAfterTransitionRunnable = null;
                        }
                    })
                    .start();
        }, 25);
+43 −22
Original line number Diff line number Diff line
@@ -464,8 +464,8 @@ public class BubbleTransitions {
     */
    @VisibleForTesting
    class LaunchNewTaskBubbleForExistingTransition implements TransitionHandler, BubbleTransition {
        final BubbleBarLayerView mLayerView;
        final BubblePositioner mPositioner;
        final BubbleExpandedViewTransitionAnimator mExpandedViewAnimator;
        private final TransitionProgress mTransitionProgress;
        Bubble mBubble;
        IBinder mTransition;
@@ -490,13 +490,17 @@ public class BubbleTransitions {
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                boolean inflateSync, IBinder transition,
                Consumer<TransitionHandler> onInflatedCallback) {
            if (layerView != null) {
                mExpandedViewAnimator = layerView;
            } else {
                mExpandedViewAnimator = stackView;
            }
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "LaunchNewTaskBubble(): expanded=%s",
                    layerView.isExpanded());
                    mExpandedViewAnimator.isExpanded());
            mBubble = bubble;
            mTransition = transition;
            mTransitionProgress = new TransitionProgress(bubble);
            mPositioner = positioner;
            mLayerView = layerView;
            mBubble.setInflateSynchronously(inflateSync);
            mBubble.setPreparingTransition(this);
            mBubble.inflate(
@@ -646,9 +650,9 @@ public class BubbleTransitions {
        private void startExpandAnim() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.startExpandAnim(): "
                    + "readyToAnimate=%b", mTransitionProgress.isReadyToAnimate());
            if (mLayerView.canExpandView(mBubble)) {
                mPriorBubble = mLayerView.prepareConvertedView(mBubble);
            } else if (mLayerView.isExpanded()) {
            if (mExpandedViewAnimator.canExpandView(mBubble)) {
                mPriorBubble = mExpandedViewAnimator.prepareConvertedView(mBubble);
            } else if (mExpandedViewAnimator.isExpanded()) {
                mTransitionProgress.setReadyToExpand();
            }
            if (mTransitionProgress.isReadyToAnimate()) {
@@ -699,12 +703,12 @@ public class BubbleTransitions {

            float startScale = 1f;
            if (mPlayConvertTaskAnimation) {
                mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot,
                mExpandedViewAnimator.animateConvert(startT, mStartBounds, startScale, mSnapshot,
                        mTaskLeash,
                        this::cleanup);
            } else {
                startT.apply();
                mLayerView.animateExpand(null, this::cleanup);
                mExpandedViewAnimator.animateExpand(null, this::cleanup);
            }
        }

@@ -720,7 +724,7 @@ public class BubbleTransitions {
     */
    @VisibleForTesting
    class LaunchOrConvertToBubble implements TransitionHandler, BubbleTransition {
        final BubbleBarLayerView mLayerView;
        final BubbleExpandedViewTransitionAnimator mExpandedViewAnimator;
        final BubblePositioner mPositioner;
        private final TransitionProgress mTransitionProgress;
        Bubble mBubble;
@@ -750,12 +754,16 @@ public class BubbleTransitions {
                BubblePositioner positioner, BubbleStackView stackView,
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                boolean inflateSync, @Nullable BubbleBarLocation bubbleBarLocation) {
            if (layerView != null) {
                mExpandedViewAnimator = layerView;
            } else {
                mExpandedViewAnimator = stackView;
            }
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "LaunchOrConvert(): expanded=%s",
                    layerView.isExpanded());
                    mExpandedViewAnimator.isExpanded());
            mBubble = bubble;
            mTransitionProgress = new TransitionProgress(bubble);
            mPositioner = positioner;
            mLayerView = layerView;
            mBubble.setInflateSynchronously(inflateSync);
            mBubble.setPreparingTransition(this);
            mBubbleBarLocation = bubbleBarLocation;
@@ -968,14 +976,14 @@ public class BubbleTransitions {
        private void startExpandAnim() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.startExpandAnim(): "
                    + "readyToAnimate=%b", mTransitionProgress.isReadyToAnimate());
            final boolean animate = mLayerView.canExpandView(mBubble);
            final boolean animate = mExpandedViewAnimator.canExpandView(mBubble);
            if (animate) {
                mPriorBubble = mLayerView.prepareConvertedView(mBubble);
                mPriorBubble = mExpandedViewAnimator.prepareConvertedView(mBubble);
            }
            if (mPriorBubble != null) {
                // TODO: an animation. For now though, just remove it.
                final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView();
                mLayerView.removeView(priorView);
                mExpandedViewAnimator.removeViewFromTransition(priorView);
                mPriorBubble = null;
            }
            if (!animate || mTransitionProgress.isReadyToAnimate()) {
@@ -1026,12 +1034,15 @@ public class BubbleTransitions {
            if (animate) {
                float startScale = 1f;
                if (mPlayConvertTaskAnimation) {
                    mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot,
                    mExpandedViewAnimator.animateConvert(startT,
                            mStartBounds,
                            startScale,
                            mSnapshot,
                            mTaskLeash,
                            this::cleanup);
                } else {
                    startT.apply();
                    mLayerView.animateExpand(null, this::cleanup);
                    mExpandedViewAnimator.animateExpand(null, this::cleanup);
                }
            } else {
                startT.apply();
@@ -1068,7 +1079,7 @@ public class BubbleTransitions {
     */
    @VisibleForTesting
    class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition {
        final BubbleBarLayerView mLayerView;
        final BubbleExpandedViewTransitionAnimator mExpandedViewAnimator;
        final BubblePositioner mPositioner;
        final HomeIntentProvider mHomeIntentProvider;
        Bubble mBubble;
@@ -1092,11 +1103,17 @@ public class BubbleTransitions {
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                HomeIntentProvider homeIntentProvider, @Nullable DragData dragData,
                boolean inflateSync) {
            if (layerView != null) {
                mExpandedViewAnimator = layerView;
            } else {
                mExpandedViewAnimator = stackView;
            }
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "ConvertToBubble(): expanded=%s",
                    mExpandedViewAnimator.isExpanded());
            mBubble = bubble;
            mTransitionProgress = new TransitionProgress(bubble);
            mTaskInfo = taskInfo;
            mPositioner = positioner;
            mLayerView = layerView;
            mHomeIntentProvider = homeIntentProvider;
            mDragData = dragData;
            mBubble.setInflateSynchronously(inflateSync);
@@ -1237,14 +1254,14 @@ public class BubbleTransitions {
        }

        private void startExpandAnim() {
            final boolean animate = mLayerView.canExpandView(mBubble);
            final boolean animate = mExpandedViewAnimator.canExpandView(mBubble);
            if (animate) {
                mPriorBubble = mLayerView.prepareConvertedView(mBubble);
                mPriorBubble = mExpandedViewAnimator.prepareConvertedView(mBubble);
            }
            if (mPriorBubble != null) {
                // TODO: an animation. For now though, just remove it.
                final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView();
                mLayerView.removeView(priorView);
                mExpandedViewAnimator.removeViewFromTransition(priorView);
                mPriorBubble = null;
            }
            if (!animate || mTransitionProgress.isReadyToAnimate()) {
@@ -1288,7 +1305,11 @@ public class BubbleTransitions {

            if (animate) {
                float startScale = mDragData != null ? mDragData.getTaskScale() : 1f;
                mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot, mTaskLeash,
                mExpandedViewAnimator.animateConvert(startT,
                        mStartBounds,
                        startScale,
                        mSnapshot,
                        mTaskLeash,
                        () -> {
                            mFinishCb.onTransitionFinished(mFinishWct);
                            mFinishCb = null;
+13 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleExpandedViewTransitionAnimator;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
@@ -74,7 +75,8 @@ import java.util.function.Consumer;
 * on screen and instead shows & animates the expanded bubble for the bubble bar.
 */
public class BubbleBarLayerView extends FrameLayout
        implements ViewTreeObserver.OnComputeInternalInsetsListener {
        implements ViewTreeObserver.OnComputeInternalInsetsListener,
        BubbleExpandedViewTransitionAnimator {

    private static final String TAG = BubbleBarLayerView.class.getSimpleName();

@@ -279,6 +281,7 @@ public class BubbleBarLayerView extends FrameLayout
    }

    /** Whether the stack of bubbles is expanded or not. */
    @Override
    public boolean isExpanded() {
        return mIsExpanded;
    }
@@ -298,6 +301,7 @@ public class BubbleBarLayerView extends FrameLayout
     * @return whether it's possible to expand {@param b} right now. This is {@code false} if
     *         the bubble has no view or if the bubble is already showing.
     */
    @Override
    public boolean canExpandView(BubbleViewProvider b) {
        if (b.getBubbleBarExpandedView() == null) return false;
        if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
@@ -307,6 +311,11 @@ public class BubbleBarLayerView extends FrameLayout
        return true;
    }

    @Override
    public void removeViewFromTransition(View view) {
        removeView(view);
    }

    /**
     * Prepares the expanded view of the provided bubble to be shown. This includes removing any
     * stale content and cancelling any related animations.
@@ -419,6 +428,7 @@ public class BubbleBarLayerView extends FrameLayout
     *                       bubble is expanded.
     * @param animFinish If non-null, the callback triggered after the expand animation completes
     */
    @Override
    public void animateExpand(BubbleViewProvider previousBubble,
            @Nullable Runnable animFinish) {
        if (!mIsExpanded || mExpandedBubble == null) {
@@ -457,6 +467,7 @@ public class BubbleBarLayerView extends FrameLayout
     * immediately so it gets a surface that can be animated. Since the surface may not be ready
     * yet, this keeps the TaskView alpha=0.
     */
    @Override
    public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) {
        final BubbleViewProvider prior = prepareExpandedView(b);

@@ -478,6 +489,7 @@ public class BubbleBarLayerView extends FrameLayout
     *
     * @param startT A transaction with first-frame work. this *will* be applied here!
     */
    @Override
    public void animateConvert(@NonNull SurfaceControl.Transaction startT,
            @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot,
            SurfaceControl taskLeash, Runnable animFinish) {
Loading