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

Commit 9fdaf8ed authored by Eric Lin's avatar Eric Lin
Browse files

Apply WCT#setTaskForceExcludedFromRecents for bubble tasks.

Applied WindowContainerTransaction to manage task exclusion from Recents
when moving a fullscreen task into a bubble or opening a new task in a
bubble. When exiting a bubble task to fullscreen via the bubble bar or
dragging the bubble icon, the exclusion is reset to allow the task to
reappear in Recents.

Previously, bubbled tasks were excluded from Recents because they were
always-on-top and in MULTI_WINDOW mode. The following changes supporting
bubble-to-fullscreen transitions use reorder instead of always-on-top
with hidden, which required disabling the always-on-top flag.
Consequently, bubbled tasks were incorrectly appearing in Recents. This
commit resolves the issue by explicitly controlling task visibility in
Recents during bubble transitions.

Bug: 404726350
Flag: com.android.window.flags.exclude_task_from_recents
Test: atest WMShellUnitTests:BubbleTransitionsTest
Test: atest WMShellUnitTests:TaskViewTransitionsTest
Test: atest WMShellRobolectricTests:BubbleExpandedViewTest
Test: atest WMShellRobolectricTests:BubbleTaskViewListenerTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleExpandedViewTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleTaskViewListenerTest
Change-Id: I65c6f097e76ecdcb16c8d0a9cb8484fd9b6485c2
parent d4394fc0
Loading
Loading
Loading
Loading
+59 −8
Original line number Diff line number Diff line
@@ -18,18 +18,32 @@ package com.android.wm.shell.bubbles

import android.content.ComponentName
import android.content.Context
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.wm.shell.Flags
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@@ -44,10 +58,29 @@ class BubbleExpandedViewTest(flags: FlagsParameterization) {
    private val context = ApplicationProvider.getApplicationContext<Context>()
    private val componentName = ComponentName(context, "TestClass")

    @Test
    fun getTaskId_onTaskCreated_returnsCorrectTaskId() {
        val bubbleTaskView = BubbleTaskView(mock<TaskView>(), directExecutor())
        val expandedView = BubbleExpandedView(context).apply {
    private val taskOrganizer = mock<ShellTaskOrganizer>()
    private val taskViewTaskToken = WindowContainerToken(mock<IWindowContainerToken> {
        on { asBinder() } doReturn mock<IBinder>()
    })
    private var taskViewController = mock<TaskViewController>()
    private val taskViewTaskController = mock<TaskViewTaskController> {
        on { taskOrganizer } doReturn taskOrganizer
        on { taskToken } doReturn taskViewTaskToken
    }

    private lateinit var taskView: TaskView
    private lateinit var bubbleTaskView: BubbleTaskView
    private lateinit var expandedView: BubbleExpandedView

    @Before
    fun setUp() {
        ProtoLog.REQUIRE_PROTOLOGTOOL = false
        ProtoLog.init()

        taskView = TaskView(context, taskViewController, taskViewTaskController)
        bubbleTaskView = BubbleTaskView(taskView, directExecutor())

        expandedView = BubbleExpandedView(context).apply {
            initialize(
                mock<BubbleExpandedViewManager>(),
                mock<BubbleStackView>(),
@@ -57,17 +90,35 @@ class BubbleExpandedViewTest(flags: FlagsParameterization) {
            )
            setAnimating(true) // Skips setContentVisibility for testing.
        }
    }

    @Test
    fun getTaskId_onTaskCreated_returnsCorrectTaskId() {
        bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)

        bubbleTaskView.listener.onTaskCreated(123, componentName)
        assertThat(expandedView.taskId).isEqualTo(123)
    }

    @Test
    @EnableFlags(FLAG_EXCLUDE_TASK_FROM_RECENTS)
    fun onTaskCreated_excludesTaskFromRecents() {
        bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)

        assertThat(expandedView.getTaskId()).isEqualTo(123)
        val wctCaptor = argumentCaptor<WindowContainerTransaction>()
        verify(taskOrganizer).applyTransaction(wctCaptor.capture())
        val wct = wctCaptor.lastValue
        assertThat(wct.changes).hasSize(1)
        val chg = wct.changes.get(taskViewTaskToken.asBinder())
        assertThat(chg).isNotNull()
        assertThat(chg!!.forceExcludedFromRecents).isTrue()
    }

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams() = FlagsParameterization.allCombinationsOf(
            Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER,
            FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER,
            FLAG_EXCLUDE_TASK_FROM_RECENTS,
        )
    }
}
+39 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.os.IBinder
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -31,13 +32,18 @@ import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
import android.view.View
import android.widget.FrameLayout
import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING
import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.taskview.TaskView
@@ -72,7 +78,15 @@ class BubbleTaskViewListenerTest {

    private val context = ApplicationProvider.getApplicationContext<Context>()

    private val taskOrganizer = mock<ShellTaskOrganizer>()
    private val taskViewTaskToken = WindowContainerToken(mock<IWindowContainerToken> {
        on { asBinder() } doReturn mock<IBinder>()
    })
    private var taskViewController = mock<TaskViewController>()
    private val taskViewTaskController = mock<TaskViewTaskController> {
        on { taskOrganizer } doReturn taskOrganizer
        on { taskToken } doReturn taskViewTaskToken
    }
    private var listenerCallback = mock<BubbleTaskViewListener.Callback>()
    private var expandedViewManager = mock<BubbleExpandedViewManager>()

@@ -92,7 +106,7 @@ class BubbleTaskViewListenerTest {
        mainExecutor = TestShellExecutor()
        bgExecutor = TestShellExecutor()

        taskView = TaskView(context, taskViewController, mock<TaskViewTaskController>())
        taskView = TaskView(context, taskViewController, taskViewTaskController)
        bubbleTaskView = BubbleTaskView(taskView, mainExecutor)

        bubbleTaskViewListener =
@@ -325,7 +339,6 @@ class BubbleTaskViewListenerTest {
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }


    @Test
    fun onInitialized_preparingTransition() {
        val b = createAppBubble()
@@ -401,6 +414,30 @@ class BubbleTaskViewListenerTest {
        assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
    }

    @Test
    @EnableFlags(FLAG_EXCLUDE_TASK_FROM_RECENTS)
    fun onTaskCreated_excludesTaskFromRecents() {
        val b = createAppBubble()
        bubbleTaskViewListener.setBubble(b)
        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(123 /* taskId */, mock<ComponentName>())
        }
        getInstrumentation().waitForIdleSync()

        val wctCaptor = argumentCaptor<WindowContainerTransaction>()
        verify(taskOrganizer).applyTransaction(wctCaptor.capture())
        val wct = wctCaptor.lastValue
        assertThat(wct.changes).hasSize(1)
        val chg = wct.changes.get(taskViewTaskToken.asBinder())
        assertThat(chg).isNotNull()
        assertThat(chg!!.forceExcludedFromRecents).isTrue()
    }

    @Test
    fun onTaskCreated_noteBubble() {
        val b = createNoteBubble()
+10 −1
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.window.ScreenCapture;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

@@ -75,6 +76,7 @@ import com.android.wm.shell.shared.TriangleShape;
import com.android.wm.shell.shared.TypefaceUtils;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTaskController;

import java.io.PrintWriter;

@@ -226,7 +228,7 @@ public class BubbleExpandedView extends LinearLayout {
                    Rect launchBounds = new Rect();
                    mTaskView.getBoundsOnScreen(launchBounds);

                    options.setTaskAlwaysOnTop(true);
                    options.setTaskAlwaysOnTop(true /* alwaysOnTop */);
                    options.setPendingIntentBackgroundActivityStartMode(
                            MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);

@@ -299,6 +301,13 @@ public class BubbleExpandedView extends LinearLayout {
                mManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
            }

            if (com.android.window.flags.Flags.excludeTaskFromRecents()) {
                final TaskViewTaskController tvc = mTaskView.getController();
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                wct.setTaskForceExcludedFromRecents(tvc.getTaskToken(), true /* forceExcluded */);
                tvc.getTaskOrganizer().applyTransaction(wct);
            }

            // With the task org, the taskAppeared callback will only happen once the task has
            // already drawn
            setContentVisibility(true);
+10 −1
Original line number Diff line number Diff line
@@ -33,12 +33,14 @@ import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTaskController;

/**
 * A listener that works with task views for bubbles, manages launching the appropriate
@@ -117,7 +119,7 @@ public class BubbleTaskViewListener implements TaskView.Listener {
            ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
                    getBubbleKey());
            try {
                options.setTaskAlwaysOnTop(true);
                options.setTaskAlwaysOnTop(true /* alwaysOnTop */);
                options.setPendingIntentBackgroundActivityStartMode(
                        MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
                final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
@@ -194,6 +196,13 @@ public class BubbleTaskViewListener implements TaskView.Listener {
            mExpandedViewManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
        }

        if (com.android.window.flags.Flags.excludeTaskFromRecents()) {
            final TaskViewTaskController tvCtrl = mTaskView.getController();
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            wct.setTaskForceExcludedFromRecents(tvCtrl.getTaskToken(), true /* forceExcluded */);
            tvCtrl.getTaskOrganizer().applyTransaction(wct);
        }

        // With the task org, the taskAppeared callback will only happen once the task has
        // already drawn
        mCallback.onTaskCreated();
+19 −10
Original line number Diff line number Diff line
@@ -283,7 +283,7 @@ public class BubbleTransitions {
            }
            final Rect launchBounds = new Rect();
            mLayerView.getExpandedViewRestBounds(launchBounds);
            WindowContainerTransaction wct = new WindowContainerTransaction();
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            mHomeIntentProvider.addLaunchHomePendingIntent(wct, mTaskInfo.displayId,
                    mTaskInfo.userId);

@@ -293,7 +293,10 @@ public class BubbleTransitions {
                }
            }

            wct.setAlwaysOnTop(mTaskInfo.token, true);
            wct.setAlwaysOnTop(mTaskInfo.token, true /* alwaysOnTop */);
            if (com.android.window.flags.Flags.excludeTaskFromRecents()) {
                wct.setTaskForceExcludedFromRecents(mTaskInfo.token, true /* forceExcluded */);
            }
            wct.setWindowingMode(mTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW);
            wct.setBounds(mTaskInfo.token, launchBounds);

@@ -525,11 +528,14 @@ public class BubbleTransitions {
            mTaskInfo = taskInfo;

            mBubble.setPreparingTransition(this);
            WindowContainerTransaction wct = new WindowContainerTransaction();
            WindowContainerToken token = mTaskInfo.getToken();
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            final WindowContainerToken token = mTaskInfo.getToken();
            wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED);
            wct.setAlwaysOnTop(token, false);
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false);
            wct.setAlwaysOnTop(token, false /* alwaysOnTop */);
            if (com.android.window.flags.Flags.excludeTaskFromRecents()) {
                wct.setTaskForceExcludedFromRecents(token, false /* forceExcluded */);
            }
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false /* intercept */);
            mTaskViewTransitions.enqueueExternal(
                    mBubble.getTaskView().getController(),
                    () -> {
@@ -686,13 +692,16 @@ public class BubbleTransitions {
            mDropLocation = dropLocation;
            mTransactionProvider = transactionProvider;
            bubble.setPreparingTransition(this);
            WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken();
            WindowContainerTransaction wct = new WindowContainerTransaction();
            wct.setAlwaysOnTop(token, false);
            final WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken();
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            wct.setAlwaysOnTop(token, false /* alwaysOnTop */);
            if (com.android.window.flags.Flags.excludeTaskFromRecents()) {
                wct.setTaskForceExcludedFromRecents(token, false /* forceExcluded */);
            }
            wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED);
            wct.reorder(token, /* onTop= */ true);
            wct.setHidden(token, false);
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false);
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false /* intercept */);
            mTaskViewTransitions.enqueueExternal(bubble.getTaskView().getController(), () -> {
                mTransition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, this);
                return mTransition;
Loading