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

Commit 8f16be13 authored by Eric Lin's avatar Eric Lin
Browse files

Extract BubbleTaskStackListener from BubbleController.

This change extracts the task stack listener into a dedicated class to
prepare for handling bubbled tasks transitioning to fullscreen. The new
BubbleTaskStackListener class provides a more flexible structure for
managing complex bubble task interactions.

Bug: 388630258
Bug: 387193964
Flag: EXEMPT refactor
Test: atest WMShellRobolectricTests:BubbleControllerTest
Test: atest WMShellRobolectricTests:BubbleTaskStackListenerTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleControllerTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleTaskStackListenerTest
Change-Id: Iee55d4ed056bc3680e7876da9187ec1a749f2a43
parent adbf06a0
Loading
Loading
Loading
Loading
+8 −1
Original line number Original line Diff line number Diff line
@@ -75,6 +75,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.isA
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verify
@@ -94,6 +95,7 @@ class BubbleControllerTest(flags: FlagsParameterization) {
    private val displayImeController = mock<DisplayImeController>()
    private val displayImeController = mock<DisplayImeController>()
    private val displayInsetsController = mock<DisplayInsetsController>()
    private val displayInsetsController = mock<DisplayInsetsController>()
    private val userManager = mock<UserManager>()
    private val userManager = mock<UserManager>()
    private val taskStackListener = mock<TaskStackListenerImpl>()


    private lateinit var bubbleController: BubbleController
    private lateinit var bubbleController: BubbleController
    private lateinit var bubblePositioner: BubblePositioner
    private lateinit var bubblePositioner: BubblePositioner
@@ -164,6 +166,11 @@ class BubbleControllerTest(flags: FlagsParameterization) {
        getInstrumentation().waitForIdleSync()
        getInstrumentation().waitForIdleSync()
    }
    }


    @Test
    fun onInit_addsBubbleTaskStackListener() {
        verify(taskStackListener).addListener(isA<BubbleTaskStackListener>())
    }

    @Test
    @Test
    fun showOrHideNotesBubble_createsNoteBubble() {
    fun showOrHideNotesBubble_createsNoteBubble() {
        val intent = Intent(context, TestActivity::class.java)
        val intent = Intent(context, TestActivity::class.java)
@@ -368,7 +375,7 @@ class BubbleControllerTest(flags: FlagsParameterization) {
                userManager,
                userManager,
                mock<LauncherApps>(),
                mock<LauncherApps>(),
                bubbleLogger,
                bubbleLogger,
                mock<TaskStackListenerImpl>(),
                taskStackListener,
                shellTaskOrganizer,
                shellTaskOrganizer,
                bubblePositioner,
                bubblePositioner,
                displayController,
                displayController,
+83 −0
Original line number Original line 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.app.ActivityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.protolog.ProtoLog
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify

/** Unit tests for [BubbleTaskStackListener]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleTaskStackListenerTest {
    private val bubble = mock<Bubble>()
    private val bubbleController = mock<BubbleController>()
    private val bubbleData = mock<BubbleData>()
    private val bubbleTaskStackListener = BubbleTaskStackListener(
        bubbleController,
        bubbleData,
    )
    private val bubbleTaskId = 123
    private val task = ActivityManager.RunningTaskInfo().apply { taskId = bubbleTaskId }

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

    @Test
    fun onActivityRestartAttempt_inStackAppBubbleRestart_selectsAndExpandsStack() {
        bubbleData.stub {
            on { getBubbleInStackWithTaskId(bubbleTaskId) } doReturn bubble
        }

        bubbleTaskStackListener.onActivityRestartAttempt(
            task,
            homeTaskVisible = false,
            clearedTask = false,
            wasVisible = false,
        )

        verify(bubbleData).setSelectedBubbleAndExpandStack(bubble)
    }

    @Test
    fun onActivityRestartAttempt_overflowAppBubbleRestart_promotesFromOverflow() {
        bubbleData.stub {
            on { getOverflowBubbleWithTaskId(bubbleTaskId) } doReturn bubble
        }

        bubbleTaskStackListener.onActivityRestartAttempt(
            task,
            homeTaskVisible = false,
            clearedTask = false,
            wasVisible = false,
        )

        verify(bubbleController).promoteBubbleFromOverflow(bubble)
        verify(bubbleData).setExpanded(true)
    }
}
+1 −25
Original line number Original line Diff line number Diff line
@@ -109,7 +109,6 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -495,30 +494,7 @@ public class BubbleController implements ConfigurationChangeListener,


        mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
        mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));


        mTaskStackListener.addListener(new TaskStackListenerCallback() {
        mTaskStackListener.addListener(new BubbleTaskStackListener(this, mBubbleData));
            @Override
            public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                    boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
                final int taskId = task.taskId;
                Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId);
                if (bubble != null) {
                    ProtoLog.d(WM_SHELL_BUBBLES,
                            "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
                            taskId, bubble.getKey());
                    mBubbleData.setSelectedBubbleAndExpandStack(bubble);
                    return;
                }

                bubble = mBubbleData.getOverflowBubbleWithTaskId(taskId);
                if (bubble != null) {
                    ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
                                    + "selecting matching overflow bubble=%s",
                            taskId, bubble.getKey());
                    promoteBubbleFromOverflow(bubble);
                    mBubbleData.setExpanded(true);
                }
            }
        });


        mDisplayController.addDisplayChangingController(
        mDisplayController.addDisplayChangingController(
                (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
                (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
+83 −0
Original line number Original line 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.app.ActivityManager
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.TaskStackListenerCallback
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES

/**
 * Listens for task stack changes and handles bubble interactions when activities are restarted.
 *
 * This class monitors task stack events to determine how bubbles should behave when their
 * associated activities are restarted. It handles scenarios where bubbles should be expanded.
 *
 * @property bubbleController The [BubbleController] to manage bubble promotions and expansions.
 * @property bubbleData The [BubbleData] to access and update bubble information.
 */
class BubbleTaskStackListener(
    private val bubbleController: BubbleController,
    private val bubbleData: BubbleData,
) : TaskStackListenerCallback {

    override fun onActivityRestartAttempt(
        task: ActivityManager.RunningTaskInfo,
        homeTaskVisible: Boolean,
        clearedTask: Boolean,
        wasVisible: Boolean,
    ) {
        val taskId = task.taskId
        bubbleData.getBubbleInStackWithTaskId(taskId)?.let { bubble ->
            selectAndExpandInStackBubble(bubble, task)
            return@onActivityRestartAttempt
        }

        bubbleData.getOverflowBubbleWithTaskId(taskId)?.let { bubble ->
            selectAndExpandOverflowBubble(bubble, task)
        }
    }

    /** Selects and expands a bubble that is currently in the stack. */
    private fun selectAndExpandInStackBubble(
        bubble: Bubble,
        task: ActivityManager.RunningTaskInfo,
    ) {
        ProtoLog.d(
            WM_SHELL_BUBBLES,
            "selectAndExpandInStackBubble - taskId=%d selecting matching bubble=%s",
            task.taskId,
            bubble.key,
        )
        bubbleData.setSelectedBubbleAndExpandStack(bubble)
    }

    /** Selects and expands a bubble that is currently in the overflow. */
    private fun selectAndExpandOverflowBubble(
        bubble: Bubble,
        task: ActivityManager.RunningTaskInfo,
    ) {
        ProtoLog.d(
            WM_SHELL_BUBBLES,
            "selectAndExpandOverflowBubble - taskId=%d selecting matching overflow bubble=%s",
            task.taskId,
            bubble.key,
        )
        bubbleController.promoteBubbleFromOverflow(bubble)
        bubbleData.setExpanded(true)
    }
}