diff options
2 files changed, 190 insertions, 25 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index a7eebd6159e4..9d445f0bb80d 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -36,6 +36,8 @@ import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R +import com.android.wm.shell.bubbles.BubbleStackView.SurfaceSynchronizer +import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.common.FloatingContentCoordinator @@ -75,6 +77,7 @@ class BubbleStackViewTest { private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory private lateinit var bubbleData: BubbleData private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager + private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer private var sysuiProxy = mock<SysuiProxy>() @Before @@ -108,13 +111,14 @@ class BubbleStackViewTest { bubbleStackViewManager = FakeBubbleStackViewManager() expandedViewManager = FakeBubbleExpandedViewManager() bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor) + surfaceSynchronizer = FakeSurfaceSynchronizer() bubbleStackView = BubbleStackView( context, bubbleStackViewManager, positioner, bubbleData, - null, + surfaceSynchronizer, FloatingContentCoordinator(), { sysuiProxy }, shellExecutor @@ -309,6 +313,7 @@ class BubbleStackViewTest { @Test fun tapDifferentBubble_shouldReorder() { + surfaceSynchronizer.isActive = false val bubble1 = createAndInflateChatBubble(key = "bubble1") val bubble2 = createAndInflateChatBubble(key = "bubble2") InstrumentationRegistry.getInstrumentation().runOnMainSync { @@ -378,6 +383,147 @@ class BubbleStackViewTest { .inOrder() } + @Test + fun tapDifferentBubble_imeVisible_shouldWaitForIme() { + val bubble1 = createAndInflateChatBubble(key = "bubble1") + val bubble2 = createAndInflateChatBubble(key = "bubble2") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble1) + bubbleStackView.addBubble(bubble2) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + assertThat(bubbleStackView.bubbleCount).isEqualTo(2) + assertThat(bubbleData.bubbles).hasSize(2) + assertThat(bubbleData.selectedBubble).isEqualTo(bubble2) + assertThat(bubble2.iconView).isNotNull() + + val expandListener = FakeBubbleExpandListener() + bubbleStackView.setExpandListener(expandListener) + + var lastUpdate: BubbleData.Update? = null + val semaphore = Semaphore(0) + val listener = + BubbleData.Listener { update -> + lastUpdate = update + semaphore.release() + } + bubbleData.setListener(listener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubble2.iconView!!.performClick() + assertThat(bubbleData.isExpanded).isTrue() + + bubbleStackView.setSelectedBubble(bubble2) + bubbleStackView.isExpanded = true + shellExecutor.flushAll() + } + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(lastUpdate!!.expanded).isTrue() + assertThat(lastUpdate!!.bubbles.map { it.key }) + .containsExactly("bubble2", "bubble1") + .inOrder() + + // wait for idle to allow the animation to start + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + // wait for the expansion animation to complete before interacting with the bubbles + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + + // make the IME visible and tap on bubble1 to select it + InstrumentationRegistry.getInstrumentation().runOnMainSync { + positioner.setImeVisible(true, 100) + bubble1.iconView!!.performClick() + // we have to set the selected bubble in the stack view manually because we don't have a + // listener wired up. + bubbleStackView.setSelectedBubble(bubble1) + shellExecutor.flushAll() + } + + val onImeHidden = bubbleStackViewManager.onImeHidden + assertThat(onImeHidden).isNotNull() + + assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true)) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + onImeHidden!!.run() + shellExecutor.flushAll() + } + + assertThat(expandListener.bubblesExpandedState) + .isEqualTo(mapOf("bubble1" to true, "bubble2" to false)) + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(bubbleData.selectedBubble).isEqualTo(bubble1) + } + + @Test + fun tapDifferentBubble_imeHidden_updatesImmediately() { + val bubble1 = createAndInflateChatBubble(key = "bubble1") + val bubble2 = createAndInflateChatBubble(key = "bubble2") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble1) + bubbleStackView.addBubble(bubble2) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + assertThat(bubbleStackView.bubbleCount).isEqualTo(2) + assertThat(bubbleData.bubbles).hasSize(2) + assertThat(bubbleData.selectedBubble).isEqualTo(bubble2) + assertThat(bubble2.iconView).isNotNull() + + val expandListener = FakeBubbleExpandListener() + bubbleStackView.setExpandListener(expandListener) + + var lastUpdate: BubbleData.Update? = null + val semaphore = Semaphore(0) + val listener = + BubbleData.Listener { update -> + lastUpdate = update + semaphore.release() + } + bubbleData.setListener(listener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubble2.iconView!!.performClick() + assertThat(bubbleData.isExpanded).isTrue() + + bubbleStackView.setSelectedBubble(bubble2) + bubbleStackView.isExpanded = true + shellExecutor.flushAll() + } + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(lastUpdate!!.expanded).isTrue() + assertThat(lastUpdate!!.bubbles.map { it.key }) + .containsExactly("bubble2", "bubble1") + .inOrder() + + // wait for idle to allow the animation to start + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + // wait for the expansion animation to complete before interacting with the bubbles + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + + // make the IME hidden and tap on bubble1 to select it + InstrumentationRegistry.getInstrumentation().runOnMainSync { + positioner.setImeVisible(false, 0) + bubble1.iconView!!.performClick() + // we have to set the selected bubble in the stack view manually because we don't have a + // listener wired up. + bubbleStackView.setSelectedBubble(bubble1) + shellExecutor.flushAll() + } + + val onImeHidden = bubbleStackViewManager.onImeHidden + assertThat(onImeHidden).isNull() + + assertThat(expandListener.bubblesExpandedState) + .isEqualTo(mapOf("bubble1" to true, "bubble2" to false)) + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(bubbleData.selectedBubble).isEqualTo(bubble1) + } + @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW) @Test fun testCreateStackView_noOverflowContents_noOverflow() { @@ -563,4 +709,18 @@ class BubbleStackViewTest { this.onImeHidden = onImeHidden } } + + private class FakeBubbleExpandListener : BubbleExpandListener { + val bubblesExpandedState = mutableMapOf<String, Boolean>() + override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) { + bubblesExpandedState[key] = isExpanding + } + } + + private class FakeSurfaceSynchronizer : SurfaceSynchronizer { + var isActive = true + override fun syncSurfaceAndRun(callback: Runnable) { + if (isActive) callback.run() + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 979d958e3c5f..1094c290df06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2183,34 +2183,39 @@ public class BubbleStackView extends FrameLayout ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s," + " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded); if (mIsExpanded) { - hideCurrentInputMethod(); - - if (Flags.enableRetrievableBubbles()) { - if (mBubbleData.getBubbles().size() == 1) { - // First bubble, check if overflow visibility needs to change - updateOverflowVisibility(); + Runnable onImeHidden = () -> { + if (Flags.enableRetrievableBubbles()) { + if (mBubbleData.getBubbles().size() == 1) { + // First bubble, check if overflow visibility needs to change + updateOverflowVisibility(); + } } - } - // Make the container of the expanded view transparent before removing the expanded view - // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the - // expanded view becomes visible on the screen. See b/126856255 - mExpandedViewContainer.setAlpha(0.0f); - mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - if (previouslySelected != null) { - previouslySelected.setTaskViewVisibility(false); - } + // Make the container of the expanded view transparent before removing the expanded + // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView} + // in the expanded view becomes visible on the screen. See b/126856255 + mExpandedViewContainer.setAlpha(0.0f); + mSurfaceSynchronizer.syncSurfaceAndRun(() -> { + if (previouslySelected != null) { + previouslySelected.setTaskViewVisibility(false); + } - updateExpandedBubble(); - requestUpdate(); + updateExpandedBubble(); + requestUpdate(); - logBubbleEvent(previouslySelected, - FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); - logBubbleEvent(bubbleToSelect, - FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); - notifyExpansionChanged(previouslySelected, false /* expanded */); - notifyExpansionChanged(bubbleToSelect, true /* expanded */); - }); + logBubbleEvent(previouslySelected, + FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); + logBubbleEvent(bubbleToSelect, + FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); + notifyExpansionChanged(previouslySelected, false /* expanded */); + notifyExpansionChanged(bubbleToSelect, true /* expanded */); + }); + }; + if (mPositioner.isImeVisible()) { + hideCurrentInputMethod(onImeHidden); + } else { + onImeHidden.run(); + } } } |