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

Commit 94017a9b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Collapse bubble on non-bubbled activity transition." into main

parents 919f5179 9472876b
Loading
Loading
Loading
Loading
+49 −26
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.app.ActivityManager;
import android.os.Binder;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.ActivityTransitionInfo;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;

@@ -53,12 +54,13 @@ public class BubblesTransitionObserver implements Transitions.TransitionObserver
    private final BubbleController mBubbleController;
    @NonNull
    private final BubbleData mBubbleData;
    @NonNull
    private final TaskViewTransitions mTaskViewTransitions;
    private final Lazy<Optional<SplitScreenController>> mSplitScreenController;

    public BubblesTransitionObserver(@NonNull BubbleController controller,
            @NonNull BubbleData bubbleData,
            TaskViewTransitions taskViewTransitions,
            @NonNull TaskViewTransitions taskViewTransitions,
            Lazy<Optional<SplitScreenController>> splitScreenController) {
        mBubbleController = controller;
        mBubbleData = bubbleData;
@@ -100,37 +102,60 @@ public class BubblesTransitionObserver implements Transitions.TransitionObserver
            if (!TransitionUtil.isOpeningType(change.getMode())) {
                continue;
            }
            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
            // We only handle task transitions.
            if (taskInfo == null || taskInfo.taskId == INVALID_TASK_ID) {
                continue;
            }
            // If the opening task id is the same as the expanded bubble, skip collapsing
            // because it is our bubble that is opening.
            if (taskInfo.taskId == expandedTaskId) {
            // If the opening transition is on a different display, skip collapsing because
            // it does not visually overlap with the bubbles.
            if (change.getEndDisplayId() != bubbleViewDisplayId) {
                continue;
            }
            // If the opening task is on a different display, skip collapsing because the task
            // opening does not visually overlap with the bubbles.
            if (taskInfo.displayId != bubbleViewDisplayId) {

            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
            final ActivityTransitionInfo activityInfo = change.getActivityTransitionInfo();
            if (taskInfo != null) {  // Task transition.
                if (shouldBypassCollapseForTask(taskInfo.taskId, expandedTaskId)) {
                    continue;
                }
            // If the opening task was launched by another bubble, skip collapsing the existing one
            // since BubbleTransitions will start a new bubble for it

                // If the opening task was launched by another bubble, skip collapsing the
                // existing one since BubbleTransitions will start a new bubble for it.
                if (BubbleAnythingFlagHelper.enableCreateAnyBubble()
                        && mBubbleController.shouldBeAppBubble(taskInfo)) {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionObserver.onTransitionReady(): "
                    ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                            "BubblesTransitionObserver.onTransitionReady(): "
                                    + "skipping app bubble for taskId=%d", taskInfo.taskId);
                    continue;
                }
            } else if (activityInfo != null) {  // Activity transition.
                if (shouldBypassCollapseForTask(activityInfo.getTaskId(), expandedTaskId)) {
                    continue;
                }
            } else {  // Invalid transition.
                continue;
            }

            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionObserver.onTransitionReady(): "
                    + "collapsing bubble for taskId=%d", taskInfo.taskId);
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubblesTransitionObserver.onTransitionReady(): "
                    + "collapse the expanded bubble for taskId=%d", expandedTaskId);
            mBubbleData.setExpanded(false);
            return;
        }
    }

    /** Checks if a task should be skipped for bubble collapse based on task ID. */
    private boolean shouldBypassCollapseForTask(int taskId, int expandedTaskId) {
        if (taskId == INVALID_TASK_ID) {
            ProtoLog.w(WM_SHELL_BUBBLES_NOISY, "BubblesTransitionObserver.onTransitionReady(): "
                    + "task id is invalid so skip collapsing");
            return true;
        }
        // If the opening task id is the same as the expanded bubble, skip collapsing
        // because it is our bubble that is opening.
        if (taskId == expandedTaskId) {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubblesTransitionObserver.onTransitionReady(): "
                    + "task %d is our bubble so skip collapsing", taskId);
            return true;
        }
        return false;
    }

    private void removeBubbleIfLaunchingToSplit(@NonNull TransitionInfo info) {
        if (mSplitScreenController.get().isEmpty()) return;
        SplitScreenController splitScreenController = mSplitScreenController.get().get();
@@ -141,10 +166,8 @@ public class BubblesTransitionObserver implements Transitions.TransitionObserver
            if (bubble == null) continue;
            if (!splitScreenController.isTaskRootOrStageRoot(taskInfo.parentTaskId)) continue;
            // There is a bubble task that is moving to split screen
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                    "TransitionObserver.onTransitionReady(): removing bubble for task launching "
                            + "into split taskId=%d",
                    taskInfo.taskId);
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubblesTransitionObserver.onTransitionReady(): "
                    + "removing bubble for task launching into split taskId=%d", taskInfo.taskId);
            TaskViewTaskController taskViewTaskController = bubble.getTaskView().getController();
            ShellTaskOrganizer taskOrganizer = taskViewTaskController.getTaskOrganizer();
            WindowContainerTransaction wct = BubbleUtilsKt.getExitBubbleTransaction(taskInfo.token,
+74 −20
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.ActivityManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.ComponentName
import android.platform.test.annotations.EnableFlags
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
@@ -27,6 +28,7 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.view.WindowManager.TransitionType
import android.window.ActivityTransitionInfo
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
@@ -40,14 +42,15 @@ import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.TransitionInfoBuilder.Companion.DEFAULT_DISPLAY_ID
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import java.util.Optional
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@@ -57,7 +60,8 @@ import org.mockito.kotlin.verifyNoInteractions
/**
 * Unit tests of [BubblesTransitionObserver].
 *
 * Build/Install/Run: atest WMShellUnitTests:BubblesTransitionObserverTest
 * Build/Install/Run:
 * atest WMShellUnitTests:BubblesTransitionObserverTest
 */
@SmallTest
@RunWith(TestParameterInjector::class)
@@ -76,7 +80,7 @@ class BubblesTransitionObserverTest : ShellTestCase() {
    }
    private val taskViewTransitions = mock<TaskViewTransitions>()
    private val splitScreenController = mock<SplitScreenController> {
        on { isTaskRootOrStageRoot(anyInt()) } doReturn false
        on { isTaskRootOrStageRoot(any()) } doReturn false
    }
    private val transitionObserver =
        BubblesTransitionObserver(
@@ -96,11 +100,37 @@ class BubblesTransitionObserverTest : ShellTestCase() {
    }

    @Test
    fun testOnTransitionReady_openTaskOnAnotherDisplay_doesNotCollapseStack() {
        val taskInfo = createTaskInfo(taskId = 2).apply {
            displayId = 1 // not DEFAULT_DISPLAY
    fun testOnTransitionReady_noneBubbleActivityTransition_collapsesStack() {
        val info = createActivityTransition(TRANSIT_OPEN, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData).setExpanded(false)
    }
        val info = createTaskTransition(TRANSIT_OPEN, taskInfo)

    @Test
    fun testOnTransitionReady_expandedBubbleActivityTransition_doesNotCollapseStack() {
        val info = createActivityTransition(TRANSIT_OPEN, taskId = 1)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_activityTransitionOnAnotherDisplay_doesNotCollapseStack() {
        val displayId = 1 // not DEFAULT_DISPLAY
        val info = createActivityTransition(TRANSIT_OPEN, taskId = 1, displayId)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_openTaskOnAnotherDisplay_doesNotCollapseStack() {
        val displayId = 1 // not DEFAULT_DISPLAY
        val info = createTaskTransition(TRANSIT_OPEN, taskId = 2, displayId)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

@@ -140,10 +170,8 @@ class BubblesTransitionObserverTest : ShellTestCase() {
    }

    @Test
    fun testOnTransitionReady_noTaskId_skip() {
        val info = createTaskTransition(TRANSIT_OPEN, taskId = INVALID_TASK_ID) // Invalid task id

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())
    fun testOnTransitionReady_noTaskId_skip(@TestParameter tc: InvalidTaskIdTestCase) {
        transitionObserver.onTransitionReady(mock(), tc.info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }
@@ -267,17 +295,43 @@ class BubblesTransitionObserverTest : ShellTestCase() {
            get() = createTaskTransition(changeType, taskId)
    }

    // Invalid task id.
    enum class InvalidTaskIdTestCase(
        private val transitionCreator: (changeType: Int, taskId: Int) -> TransitionInfo,
    ) {
        ACTIVITY_TRANSITION(transitionCreator = ::createActivityTransition),
        TASK_TRANSITION(transitionCreator = ::createTaskTransition);

        val info: TransitionInfo
            get() = transitionCreator(TRANSIT_OPEN, INVALID_TASK_ID)
    }

    companion object {
        private fun createTaskTransition(@TransitionType changeType: Int, taskId: Int) =
            createTaskTransition(changeType, taskInfo = createTaskInfo(taskId))
        private val COMPONENT = ComponentName("com.example.app", "com.example.app.MainActivity")

        private fun createTaskTransition(
            @TransitionType changeType: Int,
            taskId: Int,
            displayId: Int = DEFAULT_DISPLAY_ID,
        ) = createTaskTransition(changeType, taskInfo = createTaskInfo(taskId), displayId)

        private fun createTaskTransition(
            @TransitionType changeType: Int,
            taskInfo: ActivityManager.RunningTaskInfo?,
        ) = TransitionInfoBuilder(TRANSIT_OPEN).addChange(changeType, taskInfo).build()
            displayId: Int = DEFAULT_DISPLAY_ID,
        ) = TransitionInfoBuilder(TRANSIT_OPEN, displayId = displayId)
            .addChange(changeType, taskInfo)
            .build()

        private fun createActivityTransition(
            @TransitionType changeType: Int,
            taskId: Int,
            displayId: Int = DEFAULT_DISPLAY_ID,
        ) = TransitionInfoBuilder(TRANSIT_OPEN, displayId = displayId)
            .addChange(changeType, ActivityTransitionInfo(COMPONENT, taskId))
            .build()

        private fun createTaskInfo(taskId: Int) =
            ActivityManager.RunningTaskInfo().apply {
        private fun createTaskInfo(taskId: Int) = ActivityManager.RunningTaskInfo().apply {
            this.taskId = taskId
            this.token = MockToken().token()
            this.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+5 −3
Original line number Diff line number Diff line
@@ -33,11 +33,13 @@ import org.mockito.kotlin.mock
 * @param type the type of the transition. See [WindowManager.TransitionType].
 * @param flags the flags for the transition. See [WindowManager.TransitionFlags].
 * @param asNoOp if true, the root leash will not be added.
 * @param displayId the display ID for the root leash and transition changes.
 */
class TransitionInfoBuilder @JvmOverloads constructor(
    @WindowManager.TransitionType type: Int,
    @WindowManager.TransitionFlags flags: Int = 0,
    asNoOp: Boolean = false,
    private val displayId: Int = DEFAULT_DISPLAY_ID,
) {
    // The underlying TransitionInfo object being built.
    private val info: TransitionInfo = TransitionInfo(type, flags).apply {
@@ -46,7 +48,7 @@ class TransitionInfoBuilder @JvmOverloads constructor(
        }
        // Add a root leash by default, unless asNoOp is true.
        addRootLeash(
            DISPLAY_ID,
            displayId,
            createMockSurface(), /* leash */
            0, /* offsetLeft */
            0, /* offsetTop */
@@ -132,7 +134,7 @@ class TransitionInfoBuilder @JvmOverloads constructor(
     */
    fun addChange(change: TransitionInfo.Change): TransitionInfoBuilder {
        // Set the display ID for the change.
        change.setDisplayId(DISPLAY_ID /* start */, DISPLAY_ID /* end */)
        change.setDisplayId(displayId /* start */, displayId /* end */)
        // Add the change to the internal TransitionInfo object.
        info.addChange(change)
        return this // Return this for fluent builder pattern.
@@ -149,7 +151,7 @@ class TransitionInfoBuilder @JvmOverloads constructor(

    companion object {
        // Default display ID for root leashes and changes.
        const val DISPLAY_ID = 0
        const val DEFAULT_DISPLAY_ID = 0

        // Create a mock SurfaceControl for testing.
        private fun createMockSurface() = mock<SurfaceControl> {