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

Commit 9d037e36 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Fix issues with incorrectly handling transitions as bubble transitions" into main

parents 308ec144 f2a90024
Loading
Loading
Loading
Loading
+1 −8
Original line number Diff line number Diff line
@@ -1692,15 +1692,8 @@ public class BubbleController implements ConfigurationChangeListener,
            @NonNull IBinder transition,
            Consumer<Transitions.TransitionHandler> onInflatedCallback) {
        if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return null;
        // If there is an existing bubble then just show it
        final String taskKey = Bubble.getAppBubbleKeyForTask(taskInfo);
        if (mBubbleData.hasAnyBubbleWithKey(taskKey)) {
            ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubbleForExistingTransition(): "
                    + "skipping due to existing bubbled task=%d", taskInfo.taskId);
            return null;
        }

        // Otherwise, create a new bubble and show it
        // Create a new bubble and show it
        Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubbleForExistingTransition() taskId=%s",
                taskInfo.taskId);
+9 −3
Original line number Diff line number Diff line
@@ -136,6 +136,13 @@ public class BubbleTransitions {
        return mBubbleController.isShowingAsBubbleBar();
    }

    /**
     * Returns whether there is an existing bubble with the given task id.
     */
    public boolean hasBubbleWithTaskId(int taskId) {
        return mBubbleData.getBubbleInStackWithTaskId(taskId) != null;
    }

    /**
     * Returns whether there is a pending transition for the given request.
     */
@@ -145,8 +152,7 @@ public class BubbleTransitions {
        }
        for (IBinder cookie : info.getTriggerTask().launchCookies) {
            if (mPendingEnterTransitions.containsKey(cookie)) {
                if (mBubbleData.hasAnyBubbleWithKey(Bubble.getAppBubbleKeyForTask(
                        info.getTriggerTask()))) {
                if (hasBubbleWithTaskId(info.getTriggerTask().taskId)) {
                    // We'll let this transition fall through and let the normal TaskViewTransitions
                    // play it
                    mPendingEnterTransitions.remove(cookie);
@@ -169,7 +175,7 @@ public class BubbleTransitions {
        for (IBinder cookie : info.getTriggerTask().launchCookies) {
            final TransitionHandler handler = mPendingEnterTransitions.remove(cookie);
            if (handler != null) {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "Transfering pending to playing transition for"
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "Transferring pending to playing transition for"
                                + "cookie=%s", cookie);
                mPendingEnterTransitions.remove(cookie);
                mEnterTransitions.put(transition, handler);
+3 −1
Original line number Diff line number Diff line
@@ -641,7 +641,9 @@ public class BubbleBarAnimationHelper {
     * Cancel current animations
     */
    public void cancelAnimations() {
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BBAnimationHelper.cancelAnimations()");
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BBAnimationHelper.cancelAnimations(): "
                + "hasRunningAnimator=%b",
                (mRunningAnimator != null && mRunningAnimator.isRunning()));
        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
        BubbleBarExpandedView bbev = getExpandedView();
        if (bbev != null) {
+3 −2
Original line number Diff line number Diff line
@@ -796,7 +796,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
     * Returns whether the given request for a launching bubble and should be handled by the
     * bubbles transition.
     */
    public boolean requestHasBubbleEnter(TransitionRequestInfo request) {
    public boolean requestHasBubbleEnter(@NonNull TransitionRequestInfo request) {
        return BubbleAnythingFlagHelper.enableCreateAnyBubble()
                && request.getTriggerTask() != null
                && mBubbleTransitions.hasPendingEnterTransition(request)
@@ -808,10 +808,11 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
     * Returns whether the given request for a launching task is from an app bubble and should be
     * handled by the bubbles transition.
     */
    public boolean requestHasBubbleEnterFromAppBubble(TransitionRequestInfo request) {
    public boolean requestHasBubbleEnterFromAppBubble(@NonNull TransitionRequestInfo request) {
        return BubbleAnythingFlagHelper.enableCreateAnyBubble()
                && request.getTriggerTask() != null
                && request.getTriggerTask().isAppBubble
                && !mBubbleTransitions.hasBubbleWithTaskId(request.getTriggerTask().taskId)
                // TODO(b/408453889): To be removed once we handle transitions with stack view
                && mBubbleTransitions.isShowingAsBubbleBar();
    }
+201 −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.transition

import android.app.ActivityManager.RunningTaskInfo
import android.platform.test.annotations.EnableFlags
import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionRequestInfo
import androidx.test.filters.SmallTest
import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.activityembedding.ActivityEmbeddingController
import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.keyguard.KeyguardTransitionHandler
import com.android.wm.shell.pip.PipTransitionController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.unfold.UnfoldTransitionHandler
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub

/**
 * Tests for [DefaultMixedHandler]
 * Build & Run: atest WMShellUnitTests:DefaultMixedHandlerTest
 */
@SmallTest
class DefaultMixedHandlerTest : ShellTestCase() {

    private val transitions = mock<Transitions>()
    private val splitScreenController = mock<SplitScreenController>()
    private val pipTransitionController = mock<PipTransitionController>()
    private val recentsTransitionHandler = mock<RecentsTransitionHandler>()
    private val keyguardTransitionHandler = mock<KeyguardTransitionHandler>()
    private val desktopTasksController = mock<DesktopTasksController>()
    private val unfoldTransitionHandler = mock<UnfoldTransitionHandler>()
    private val activityEmbeddingController = mock<ActivityEmbeddingController>()
    private val bubbleTransitions = mock<BubbleTransitions>()

    private val shellInit: ShellInit = ShellInit(TestShellExecutor())
    private val mixedHandler = DefaultMixedHandler(
        shellInit,
        transitions,
        Optional.of(splitScreenController),
        pipTransitionController,
        Optional.of(recentsTransitionHandler),
        keyguardTransitionHandler,
        Optional.of(desktopTasksController),
        Optional.of(unfoldTransitionHandler),
        Optional.of(activityEmbeddingController),
        bubbleTransitions,
    )

    @Before
    fun setUp() {
        shellInit.init()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnter_noTriggerTask_notHandled() {
        val noTriggerTaskRequest = createTransitionRequestInfo()

        assertThat(mixedHandler.requestHasBubbleEnter(noTriggerTaskRequest)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnter_noPendingEnterTransition_notHandled() {
        val runningTask = createRunningTask()
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn false
        }

        assertThat(mixedHandler.requestHasBubbleEnter(request)).isFalse()
    }

    // TODO(b/408453889): To be removed once we handle transitions with stack view
    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnter_notShowingAsBubbleBar_notHandled() {
        val runningTask = createRunningTask()
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn true
            on { isShowingAsBubbleBar } doReturn false
        }

        assertThat(mixedHandler.requestHasBubbleEnter(request)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnter() {
        val runningTask = createRunningTask()
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn true
            on { isShowingAsBubbleBar } doReturn true
        }

        assertThat(mixedHandler.requestHasBubbleEnter(request)).isTrue()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnterFromAppBubble_noTriggerTask_notHandled() {
        val noTriggerTaskRequest = createTransitionRequestInfo()

        assertThat(mixedHandler.requestHasBubbleEnterFromAppBubble(noTriggerTaskRequest)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnterFromAppBubble_notAppBubble_notHandled() {
        val runningTask = createRunningTask(100, false)
        val request = createTransitionRequestInfo(runningTask)

        assertThat(mixedHandler.requestHasBubbleEnterFromAppBubble(request)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnterFromAppBubble_hasExistingBubbleTask_notHandled() {
        val runningTask = createRunningTask(100, true)
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasBubbleWithTaskId(100) } doReturn true
        }

        assertThat(mixedHandler.requestHasBubbleEnterFromAppBubble(request)).isFalse()
    }

    // TODO(b/408453889): To be removed once we handle transitions with stack view
    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnterFromAppBubble_notShowingAsBubbleBar_notHandled() {
        val runningTask = createRunningTask(100, true)
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasBubbleWithTaskId(100) } doReturn false
            on { isShowingAsBubbleBar } doReturn false
        }

        assertThat(mixedHandler.requestHasBubbleEnterFromAppBubble(request)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    fun test_requestHasBubbleEnterFromAppBubble() {
        val runningTask = createRunningTask(100, true)
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { hasBubbleWithTaskId(100) } doReturn false
            on { isShowingAsBubbleBar } doReturn true
        }

        assertThat(mixedHandler.requestHasBubbleEnterFromAppBubble(request)).isTrue()
    }

    private fun createTransitionRequestInfo(
        runningTask: RunningTaskInfo? = null
    ): TransitionRequestInfo {
        return TransitionRequestInfo(TRANSIT_OPEN, runningTask, null /* remoteTransition */)
    }

    private fun createRunningTask(taskId: Int = 0, isAppBubble: Boolean = false): RunningTaskInfo {
        return RunningTaskInfo().apply {
            this.taskId = taskId
            this.isAppBubble = isAppBubble
        }
    }
}