Merge "Add new SystemUI AnimatorTestRule that wraps both platform and androidx versions of the same" into udc-qpr-dev

This commit is contained in:
Treehugger Robot
2023-08-04 13:32:03 +00:00
committed by Android (Google) Code Review
15 changed files with 284 additions and 13 deletions

View File

@@ -407,6 +407,7 @@ android_library {
static_libs: [
"SystemUI-tests-base",
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"mockito-target-extended-minus-junit4",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
@@ -476,6 +477,7 @@ android_robolectric_test {
],
static_libs: [
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
"inline-mockito-robolectric-prebuilt",
],

View File

@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWithLooper
class AnimatorTestRuleIsolationTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()

View File

@@ -28,7 +28,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWithLooper
class AnimatorTestRulePrecisionTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()

View File

@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWithLooper
class AnimatorTestRuleIsolationTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()

View File

@@ -25,12 +25,12 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.core.animation.AnimatorTestRule;
import androidx.core.animation.ObjectAnimator;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationMediaManager;

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 2023 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.systemui.animation
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
class AnimatorTestRuleOrderTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()
var value1: Float = -1f
var value2: Float = -1f
private inline fun animateThisX(
propertyName: String,
duration: Long,
startDelay: Long = 0,
crossinline onEndAction: () -> Unit,
) {
androidx.core.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
it.interpolator = null
it.duration = duration
it.startDelay = startDelay
it.doOnEnd { onEndAction() }
it.start()
}
}
private inline fun animateThisP(
propertyName: String,
duration: Long,
startDelay: Long = 0,
crossinline onEndAction: () -> Unit,
) {
android.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
it.interpolator = null
it.duration = duration
it.startDelay = startDelay
it.doOnEnd { onEndAction() }
it.start()
}
}
@Test
fun testTwoAnimators() {
var ended1 = false
var ended2 = false
animateThisP("value1", duration = 100) { ended1 = true }
animateThisX("value2", duration = 200) { ended2 = true }
assertThat(value1).isEqualTo(0f)
assertThat(value2).isEqualTo(0f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(99)
assertThat(value1).isEqualTo(0.99f)
assertThat(value2).isEqualTo(0.495f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(1)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0.5f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(99)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0.995f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(1)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(1f)
assertThat(ended1).isTrue()
assertThat(ended2).isTrue()
}
@Test
fun testChainedAnimatorsPlatformThenX() {
var ended1 = false
var ended2 = false
animateThisP("value1", duration = 100) {
ended1 = true
animateThisX("value2", duration = 100) { ended2 = true }
}
assertThat(value1).isEqualTo(0f)
assertThat(value2).isEqualTo(-1f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(0.5f)
assertThat(value2).isEqualTo(-1f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0.5f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(1f)
assertThat(ended1).isTrue()
assertThat(ended2).isTrue()
}
@Test
fun testChainedAnimatorsXThenPlatform() {
var ended1 = false
var ended2 = false
animateThisX("value1", duration = 100) {
ended1 = true
animateThisP("value2", duration = 100) { ended2 = true }
}
assertThat(value1).isEqualTo(0f)
assertThat(value2).isEqualTo(-1f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(0.5f)
assertThat(value2).isEqualTo(-1f)
assertThat(ended1).isFalse()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(0.5f)
assertThat(ended1).isTrue()
assertThat(ended2).isFalse()
animatorTestRule.advanceTimeBy(50)
assertThat(value1).isEqualTo(1f)
assertThat(value2).isEqualTo(1f)
assertThat(ended1).isTrue()
assertThat(ended2).isTrue()
}
}

View File

@@ -19,10 +19,10 @@ package com.android.systemui.keyguard.data.repository
import android.graphics.Point
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource

View File

@@ -24,9 +24,9 @@ import android.util.Pair
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider

View File

@@ -22,9 +22,9 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags

View File

@@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController

View File

@@ -45,12 +45,12 @@ import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.LogBuffer;

View File

@@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone.fragment
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Rule

View File

@@ -62,7 +62,6 @@ import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -70,6 +69,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationRemoteInputManager;

View File

@@ -18,6 +18,7 @@ package android.animation;
import android.animation.AnimationHandler.AnimationFrameCallback;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Looper;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
@@ -31,6 +32,7 @@ import org.junit.runners.model.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* JUnit {@link TestRule} that can be used to run {@link Animator}s without actually waiting for the
@@ -125,14 +127,40 @@ public final class AnimatorTestRule implements TestRule {
* @param timeDelta the amount of milliseconds to advance
*/
public void advanceTimeBy(long timeDelta) {
advanceTimeBy(timeDelta, null);
}
/**
* Advances the animation clock by the given amount of delta in milliseconds. This call will
* produce an animation frame to all the ongoing animations. This method needs to be
* called on the same thread as {@link Animator#start()}.
* <p>
* This method is not for test authors, but for rule authors to ensure that multiple animators
* can be advanced in sync.
*
* @param timeDelta the amount of milliseconds to advance
* @param preFrameAction a consumer to be passed the timeDelta following the time advancement
* but prior to the frame production.
*/
public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) {
Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative");
requireLooper("AnimationTestRule#advanceTimeBy(long)");
// before advancing time, start new animators with the current time
initNewAnimators();
if (timeDelta == 0) {
// If time is not being advanced, all animators will get a tick; don't double tick these
mTestHandler.mNewCallbacks.clear();
} else {
// before advancing time, start new animators with the current time
initNewAnimators();
}
synchronized (mLock) {
// advance time
mTotalTimeDelta += timeDelta;
}
if (preFrameAction != null) {
preFrameAction.accept(timeDelta);
// After letting other code run, clear any new callbacks to avoid double-ticking them
mTestHandler.mNewCallbacks.clear();
}
// produce a frame
mTestHandler.doFrame();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 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.systemui.animation
import java.util.function.Consumer
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* A rule that wraps both [androidx.core.animation.AnimatorTestRule] and
* [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be
* advanced together.
*/
class AnimatorTestRule : TestRule {
private val androidxRule = androidx.core.animation.AnimatorTestRule()
private val platformRule = android.animation.AnimatorTestRule()
private val advanceAndroidXTimeBy =
Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) }
/**
* Chain is for simplicity not to force a particular order; order should not matter, because
* each rule affects a different AnimationHandler classes, and no callbacks to code under test
* should be triggered by these rules
*/
private val ruleChain = RuleChain.emptyRuleChain().around(androidxRule).around(platformRule)
override fun apply(base: Statement, description: Description): Statement =
ruleChain.apply(base, description)
/**
* Advances the animation clock by the given amount of delta in milliseconds. This call will
* produce an animation frame to all the ongoing animations.
*
* @param timeDelta the amount of milliseconds to advance
*/
fun advanceTimeBy(timeDelta: Long) {
// NOTE: To avoid errors with order, we have to ensure that we advance the time within both
// rules before either rule does its frame output. Failing to do this could cause the
// animation from one to start later than the other.
platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy)
}
}