Fix onStart/EndAction() for ViewPropertyAnimatorCompat
These methods are meant to be single-action only (removed when they
are activated). That's the way they work on the framework; it was an oversight
to have them be persistent in the ICS version of the support library.
Issue #25764942 ViewPropertyAnimatorCompatICS end action is not cleared
Change-Id: I0825330c76cecf63960d79f74116acd99b874830
(cherry picked from commit fdd92b54cc21d9927ee89cf675cf4c90c306f24a)
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
index a28c101..3427e26 100644
--- a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
@@ -298,6 +298,8 @@
}
Runnable startAction = vpa.mStartAction;
Runnable endAction = vpa.mEndAction;
+ vpa.mStartAction = null;
+ vpa.mEndAction = null;
if (startAction != null) {
startAction.run();
}
@@ -538,7 +540,9 @@
ViewCompat.setLayerType(view, ViewCompat.LAYER_TYPE_HARDWARE, null);
}
if (mVpa.mStartAction != null) {
- mVpa.mStartAction.run();
+ Runnable startAction = mVpa.mStartAction;
+ mVpa.mStartAction = null;
+ startAction.run();
}
Object listenerTag = view.getTag(LISTENER_TAG_ID);
ViewPropertyAnimatorListener listener = null;
@@ -560,7 +564,9 @@
// Pre-v16 seems to have a bug where onAnimationEnd is called
// twice, therefore we only dispatch on the first call
if (mVpa.mEndAction != null) {
- mVpa.mEndAction.run();
+ Runnable endAction = mVpa.mEndAction;
+ mVpa.mEndAction = null;
+ endAction.run();
}
Object listenerTag = view.getTag(LISTENER_TAG_ID);
ViewPropertyAnimatorListener listener = null;
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index 85ffd7d..f0fea8f 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -38,6 +38,8 @@
<activity android:name="android.support.v4.widget.TestActivity"/>
+ <activity android:name="android.support.v4.view.VpaActivity"/>
+
<activity
android:name="android.support.v4.ThemedYellowActivity"
android:theme="@style/YellowTheme" />
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
new file mode 100644
index 0000000..13a78dc
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 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 android.support.v4.view;
+
+import android.app.Activity;
+import android.support.test.InstrumentationRegistry;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
+
+ private View mView;
+ private int mVariable;
+ private int mNumListenerCalls = 0;
+
+ public ViewPropertyAnimatorCompatTest() {
+ super(VpaActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final Activity activity = mActivityTestRule.getActivity();
+ mView = activity.findViewById(R.id.view);
+ }
+
+ @Test
+ @MediumTest
+ public void testWithEndAction() throws Throwable {
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(100).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ latch1.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch1.await(300, TimeUnit.MILLISECONDS));
+
+ // This test ensures that the endAction listener will be called exactly once
+ mNumListenerCalls = 0;
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(50).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ ++mNumListenerCalls;
+ ViewCompat.animate(mView).alpha(1);
+ latch2.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
+ // Now sleep to allow second listener callback to happen, if it will
+ Thread.sleep(200);
+ assertEquals(1, mNumListenerCalls);
+ }
+
+ @Test
+ @MediumTest
+ public void testWithStartAction() throws Throwable {
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(100).withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ latch1.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch1.await(100, TimeUnit.MILLISECONDS));
+
+ // This test ensures that the startAction listener will be called exactly once
+ mNumListenerCalls = 0;
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(50).withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ ++mNumListenerCalls;
+ ViewCompat.animate(mView).alpha(1);
+ latch2.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
+ // Now sleep to allow second listener callback to happen, if it will
+ Thread.sleep(200);
+ assertEquals(1, mNumListenerCalls);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/VpaActivity.java b/v4/tests/java/android/support/v4/view/VpaActivity.java
new file mode 100644
index 0000000..d84d62d
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/VpaActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 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 android.support.v4.view;
+
+import android.app.Activity;
+import android.support.v4.test.R;
+import android.os.Bundle;
+
+public class VpaActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.vpa_activity);
+ }
+}
diff --git a/v4/tests/res/layout/vpa_activity.xml b/v4/tests/res/layout/vpa_activity.xml
new file mode 100644
index 0000000..2363160
--- /dev/null
+++ b/v4/tests/res/layout/vpa_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/view"
+ android:background="#f00"
+ android:layout_width="400dp"
+ android:layout_height="200dp"/>
+
+</LinearLayout>