Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +1 −8 Original line number Diff line number Diff line Loading @@ -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); Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +9 −3 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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); Loading @@ -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); Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +3 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +3 −2 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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(); } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultMixedHandlerTest.kt 0 → 100644 +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 } } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +1 −8 Original line number Diff line number Diff line Loading @@ -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); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +9 −3 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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); Loading @@ -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); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +3 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +3 −2 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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(); } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultMixedHandlerTest.kt 0 → 100644 +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 } } }