summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt153
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt11
3 files changed, 150 insertions, 28 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 8989fc543044..f3d70f7c160b 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
@@ -18,27 +18,32 @@ package com.android.wm.shell.bubbles
import android.content.Context
import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.view.IWindowManager
import android.view.WindowManager
import android.view.WindowManagerGlobal
-import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.common.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.taskview.TaskView
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.After
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -64,6 +69,7 @@ class BubbleStackViewTest {
@Before
fun setUp() {
+ PhysicsAnimatorTestUtils.prepareForTest()
// Disable protolog tool when running the tests from studio
ProtoLog.REQUIRE_PROTOLOGTOOL = false
windowManager = WindowManagerGlobal.getWindowManagerService()!!
@@ -104,34 +110,158 @@ class BubbleStackViewTest {
{ sysuiProxy },
shellExecutor
)
+
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true)
+ .apply()
+ }
+
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
}
- @UiThreadTest
@Test
fun addBubble() {
val bubble = createAndInflateBubble()
- bubbleStackView.addBubble(bubble)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
}
- @UiThreadTest
@Test
fun tapBubbleToExpand() {
val bubble = createAndInflateBubble()
- bubbleStackView.addBubble(bubble)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble.iconView!!.performClick()
+ // we're checking the expanded state in BubbleData because that's the source of truth.
+ // This will eventually propagate an update back to the stack view, but setting the
+ // entire pipeline is outside the scope of a unit test.
+ assertThat(bubbleData.isExpanded).isTrue()
+ }
- bubble.iconView!!.performClick()
- // we're checking the expanded state in BubbleData because that's the source of truth. This
- // will eventually propagate an update back to the stack view, but setting the entire
- // pipeline is outside the scope of a unit test.
- assertThat(bubbleData.isExpanded).isTrue()
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate).isNotNull()
+ assertThat(lastUpdate!!.expandedChanged).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ }
+
+ @Test
+ fun tapDifferentBubble_shouldReorder() {
+ 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()
+
+ 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
+ }
+
+ 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)
+
+ // tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble1.iconView!!.performClick()
+ }
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+
+ // tap on bubble1 again to collapse the stack
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ bubble1.iconView!!.performClick()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ assertThat(bubbleData.isExpanded).isFalse()
+ assertThat(lastUpdate!!.orderChanged).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble1", "bubble2")
+ .inOrder()
+ }
+
+ private fun createAndInflateChatBubble(key: String): Bubble {
+ val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+ val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
+ val bubble =
+ Bubble(
+ key,
+ shortcutInfo,
+ /* desiredHeight= */ 6,
+ Resources.ID_NULL,
+ "title",
+ /* taskId= */ 0,
+ "locus",
+ /* isDismissable= */ true,
+ directExecutor()
+ ) {}
+ inflateBubble(bubble)
+ return bubble
}
private fun createAndInflateBubble(): Bubble {
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ inflateBubble(bubble)
+ return bubble
+ }
+
+ private fun inflateBubble(bubble: Bubble) {
bubble.setInflateSynchronously(true)
bubbleData.notificationEntryUpdated(bubble, true, false)
@@ -152,7 +282,6 @@ class BubbleStackViewTest {
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
assertThat(bubble.isInflated).isTrue()
- return bubble
}
private class FakeBubbleStackViewManager : BubbleStackViewManager {
@@ -176,7 +305,7 @@ class BubbleStackViewTest {
r.run()
}
- override fun removeCallbacks(r: Runnable) {}
+ override fun removeCallbacks(r: Runnable?) {}
override fun hasCallback(r: Runnable): Boolean = false
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index ee8c41417458..b7f0890ec2bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -505,7 +505,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
// Check for a spring configuration. If one is present, we're either springing, or
// flinging-then-springing.
if (springConfig != null) {
-
// If there is no corresponding fling config, we're only springing.
if (flingConfig == null) {
// Apply the configuration and start the animation.
@@ -679,7 +678,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
value: Float,
velocity: Float
) {
-
// If this property animation isn't relevant to this listener, ignore it.
if (!properties.contains(property)) {
return
@@ -702,7 +700,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
finalVelocity: Float,
isFling: Boolean
): Boolean {
-
// If this property animation isn't relevant to this listener, ignore it.
if (!properties.contains(property)) {
return false
@@ -971,17 +968,18 @@ class PhysicsAnimator<T> private constructor (target: T) {
companion object {
/**
- * Constructor to use to for new physics animator instances in [getInstance]. This is
- * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
- * all code using the physics animator is given testable instances instead.
+ * Callback to notify that a new animator was created. Used in [PhysicsAnimatorTestUtils]
+ * to be able to keep track of animators and wait for them to finish.
*/
- internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+ internal var onAnimatorCreated: (PhysicsAnimator<*>, Any) -> Unit = { _, _ -> }
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
if (!animators.containsKey(target)) {
- animators[target] = instanceConstructor(target)
+ val animator = PhysicsAnimator(target)
+ onAnimatorCreated(animator, target)
+ animators[target] = animator
}
return animators[target] as PhysicsAnimator<T>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
index 86eb8da952f1..7defc26eef35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
@@ -62,12 +62,9 @@ object PhysicsAnimatorTestUtils {
*/
@JvmStatic
fun prepareForTest() {
- val defaultConstructor = PhysicsAnimator.instanceConstructor
- PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
- val animator = defaultConstructor(target)
+ PhysicsAnimator.onAnimatorCreated = { animator, target ->
allAnimatedObjects.add(target)
animatorTestHelpers[animator] = AnimatorTestHelper(animator)
- return animator
}
timeoutMs = 2000
@@ -158,12 +155,12 @@ object PhysicsAnimatorTestUtils {
@Throws(InterruptedException::class)
@Suppress("UNCHECKED_CAST")
fun <T : Any> blockUntilAnimationsEnd(
- properties: FloatPropertyCompat<in T>
+ vararg properties: FloatPropertyCompat<in T>
) {
for (target in allAnimatedObjects) {
try {
blockUntilAnimationsEnd(
- PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+ PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, *properties)
} catch (e: ClassCastException) {
// Keep checking the other objects for ones whose types match the provided
// properties.
@@ -267,10 +264,8 @@ object PhysicsAnimatorTestUtils {
// Loop through the updates from the testable animator.
for (update in framesForProperty) {
-
// Check whether this frame satisfies the current matcher.
if (curMatcher(update)) {
-
// If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
// frames and return without failing.
if (matchers.size == 0) {