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

Commit 3c18ddf2 authored by Eric Lin's avatar Eric Lin
Browse files

Allow launch-next-bubble transition for existing bubbles.

This change enables proper transition handling when launching an already
bubbled task. Previously, scenarios for
TYPE_LAUNCH_OR_CONVERT_TO_BUBBLE_FROM_EXISTING_BUBBLE did not account
for existing bubbles. Key modifications include updating
BubbleController to reuse an existing bubble and DefaultMixedHandler to
accept requests for already bubbled tasks. Consequently,
BubbleTransitions and DefaultMixedTransition can now appropriately
manage and animate these transitions. This resolves issues where
launching from a notification with a collapsed bubble did not expand it,
and launching from a notification with an expanded bubble did not switch
to it.

Bug: 407935283
Bug: 414501728
Bug: 416136909
Bug: 389810202
Flag: EXEMPT bug fix
Test: atest WMShellRobolectricTests:BubbleControllerTest
Test: atest WMShellRobolectricTests:BubbleTaskStackListenerTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleControllerTest
Test: atest WMShellMultivalentTestsOnDevice:BubbleTaskStackListenerTest
Test: atest WMShellUnitTests:DefaultMixedHandlerTest
Change-Id: I277082166682478182984b74b0f22b05c3593641
parent 38415849
Loading
Loading
Loading
Loading
+80 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.wm.shell.bubbles

import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
@@ -77,9 +79,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.isA
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -99,6 +103,7 @@ class BubbleControllerTest(flags: FlagsParameterization) {
    @get:Rule
    val setFlagsRule = SetFlagsRule(flags)

    private val bubbleTransitions = mock<BubbleTransitions>()
    private val context = ApplicationProvider.getApplicationContext<Context>()
    private val uiEventLoggerFake = UiEventLoggerFake()
    private val displayImeController = mock<DisplayImeController>()
@@ -389,6 +394,78 @@ class BubbleControllerTest(flags: FlagsParameterization) {
        assertThat(bubbleController.hasStableBubbleForTask(777)).isFalse()
    }

    @Test
    fun expandStackAndSelectBubbleForExistingTransition_reusesExistingBubble() {
        assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble())

        val bubble = createBubble("key", taskId = 123)
        getInstrumentation().runOnMainSync {
            bubbleData.notificationEntryUpdated(
                bubble,
                true /* suppressFlyout */,
                true /* showInShade= */,
            )
        }
        val taskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 }

        getInstrumentation().runOnMainSync {
            bubbleController.expandStackAndSelectBubbleForExistingTransition(
                taskInfo,
                mock(), /* transition */
            ) {}
        }

        verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition(
            eq(bubble), /* bubble */
            any(), /* expandedViewManager */
            any(), /* factory */
            any(), /* positioner */
            any(), /* stackView */
            anyOrNull(), /* layerView */
            any(), /* iconFactory */
            any(), /* inflateSync */
            any(), /* transition */
            any(), /* onInflatedCallback */
        )
        assertThat(bubbleData.selectedBubble).isEqualTo(bubble)
        assertThat(bubbleController.isStackExpanded).isTrue()
    }

    @Test
    fun expandStackAndSelectBubbleForExistingTransition_newBubble() {
        assumeTrue(BubbleAnythingFlagHelper.enableCreateAnyBubble())

        val taskInfo = ActivityManager.RunningTaskInfo().apply {
            baseActivity = COMPONENT
            taskId = 123
        }

        getInstrumentation().runOnMainSync {
            bubbleController.expandStackAndSelectBubbleForExistingTransition(
                taskInfo,
                mock(), /* transition */
            ) {}
        }

        val bubble = argumentCaptor<Bubble>().let { bubbleCaptor ->
            verify(bubbleTransitions).startLaunchNewTaskBubbleForExistingTransition(
                bubbleCaptor.capture(), /* bubble */
                any(), /* expandedViewManager */
                any(), /* factory */
                any(), /* positioner */
                any(), /* stackView */
                anyOrNull(), /* layerView */
                any(), /* iconFactory */
                any(), /* inflateSync */
                any(), /* transition */
                any(), /* onInflatedCallback */
            )
            bubbleCaptor.lastValue
        }
        assertThat(bubble.taskId).isEqualTo(123)
        assertThat(bubble.key).isEqualTo("key_app_bubble:123")
    }

    private fun createBubble(key: String, taskId: Int = 0): Bubble {
        val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
        val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
@@ -459,7 +536,7 @@ class BubbleControllerTest(flags: FlagsParameterization) {
                surfaceSynchronizer,
                FloatingContentCoordinator(),
                bubbleDataRepository,
                mock<BubbleTransitions>(),
                bubbleTransitions,
                mock<IStatusBarService>(),
                windowManager,
                displayInsetsController,
@@ -500,6 +577,8 @@ class BubbleControllerTest(flags: FlagsParameterization) {
    }

    companion object {
        private val COMPONENT = ComponentName("com.example.app", "com.example.app.MainActivity")

        private fun createFakeInsetsState(imeVisible: Boolean): InsetsState {
            val insetsState = InsetsState()
            if (imeVisible) {
+22 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles

import android.app.ActivityManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.os.Binder
import android.os.IBinder
@@ -46,6 +47,7 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
@@ -114,6 +116,26 @@ class BubbleTaskStackListenerTest {
        verify(bubbleData).setSelectedBubbleAndExpandStack(bubble)
    }

    @Test
    fun onActivityRestartAttempt_inStackAppBubbleMovingToFront_doesNothing() {
        task.configuration.windowConfiguration.activityType = ACTIVITY_TYPE_STANDARD
        bubbleController.stub {
            on { shouldBeAppBubble(task) } doReturn true
        }
        bubbleData.stub {
            on { getBubbleInStackWithTaskId(bubbleTaskId) } doReturn bubble
        }

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

        verify(bubbleData, never()).setSelectedBubbleAndExpandStack(bubble)
    }

    @Test
    @EnableFlags(
        FLAG_ENABLE_CREATE_ANY_BUBBLE,
+8 −2
Original line number Diff line number Diff line
@@ -1693,8 +1693,14 @@ public class BubbleController implements ConfigurationChangeListener,
            Consumer<Transitions.TransitionHandler> onInflatedCallback) {
        if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return null;

        // Create a new bubble and show it
        Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
        Bubble b = mBubbleData.getBubbleInStackWithTaskId(taskInfo.taskId);
        if (b != null) {
            // Reuse the existing bubble
            mBubbleData.setSelectedBubbleAndExpandStack(b, BubbleBarLocation.DEFAULT);
        } else {
            // Create a new bubble and show it, remove from overflow
            b = mBubbleData.getOrCreateBubble(taskInfo);
        }
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubbleForExistingTransition() taskId=%s",
                taskInfo.taskId);
        b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+13 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
package com.android.wm.shell.bubbles

import android.app.ActivityManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
@@ -64,7 +65,7 @@ class BubbleTaskStackListener(
            when {
                isBubbleToFullscreen(task) -> moveCollapsedInStackBubbleToFullscreen(bubble, task)
                isBubbleToSplit(task) -> return // skip split task restarts
                else -> selectAndExpandInStackBubble(bubble, task)
                !isAppBubbleMovingToFront(task) -> selectAndExpandInStackBubble(bubble, task)
            }
        }
    }
@@ -75,6 +76,17 @@ class BubbleTaskStackListener(
            .orElse(false)
    }

    /**
     * Returns whether the given bubble task restart should move the app bubble to front
     * and be handled in DefaultMixedTransition#animateEnterBubblesFromBubble.
     * This occurs when a startActivity call resolves to an existing activity, causing the
     * task to move to front, and the mixed transition will then expand the bubble.
     */
    private fun isAppBubbleMovingToFront(task: ActivityManager.RunningTaskInfo): Boolean {
        return task.activityType == ACTIVITY_TYPE_STANDARD
                && bubbleController.shouldBeAppBubble(task)
    }

    /** Selects and expands a bubble that is currently in the stack. */
    private fun selectAndExpandInStackBubble(
        bubble: Bubble,
+7 −9
Original line number Diff line number Diff line
@@ -165,12 +165,6 @@ public class BubbleTransitions {
        }
        for (IBinder cookie : info.getTriggerTask().launchCookies) {
            if (mPendingEnterTransitions.containsKey(cookie)) {
                if (hasBubbleWithTaskId(info.getTriggerTask().taskId)) {
                    // We'll let this transition fall through and let the normal TaskViewTransitions
                    // play it
                    mPendingEnterTransitions.remove(cookie);
                    return false;
                }
                return true;
            }
        }
@@ -245,11 +239,11 @@ public class BubbleTransitions {
    }

    /**
     * Called to initiate axed bubble-to-bubble launch/convert for the given transition.
     * Initiates axed bubble-to-bubble launch/existing bubble convert for the given transition.
     *
     * @return whether a new transition was started for the launch
     */
    public boolean startBubbleToBubbleLaunch(@NonNull IBinder transition,
    public boolean startBubbleToBubbleLaunchOrExistingBubbleConvert(@NonNull IBinder transition,
            @NonNull ActivityManager.RunningTaskInfo launchingTask,
            @NonNull Consumer<TransitionHandler> onInflatedCallback) {
        TransitionHandler handler =
@@ -538,6 +532,9 @@ public class BubbleTransitions {
                state.mVisible = true;
            }
            mTransitionProgress.setInflated();
            // Remove any intermediate queued transitions that were started as a result of the
            // inflation (the task view will be in the right bounds)
            mTaskViewTransitions.removePendingTransitions(tv.getController());
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> {
                return mTransition;
            });
@@ -681,7 +678,8 @@ public class BubbleTransitions {
        }

        private void playAnimation() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.playAnimation()");
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.playAnimation(): playConvert=%b",
                    mPlayConvertTaskAnimation);
            final TaskViewTaskController tv = mBubble.getTaskView().getController();
            final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
            // Set task position to 0,0 as it will be placed inside the TaskView
Loading