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

Commit 98d565c3 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Add & implement interface for BubbleTransitions to animate bubble views" into main

parents bb958d26 1158a084
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