summaryrefslogtreecommitdiff
path: root/core/java/android/animation/AnimationHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/animation/AnimationHandler.java')
-rw-r--r--core/java/android/animation/AnimationHandler.java110
1 files changed, 109 insertions, 1 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 260323fe2d10..7f6df2261fcc 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -18,6 +18,8 @@ package android.animation;
import android.os.SystemClock;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
import android.view.Choreographer;
import java.util.ArrayList;
@@ -35,10 +37,13 @@ import java.util.ArrayList;
* @hide
*/
public class AnimationHandler {
+
+ private static final String TAG = "AnimationHandler";
+ private static final boolean LOCAL_LOGV = true;
+
/**
* Internal per-thread collections used to avoid set collisions as animations start and end
* while being processed.
- * @hide
*/
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
new ArrayMap<>();
@@ -48,6 +53,26 @@ public class AnimationHandler {
new ArrayList<>();
private AnimationFrameCallbackProvider mProvider;
+ /**
+ * This paused list is used to store animators forcibly paused when the activity
+ * went into the background (to avoid unnecessary background processing work).
+ * These animators should be resume()'d when the activity returns to the foreground.
+ */
+ private final ArrayList<Animator> mPausedAnimators = new ArrayList<>();
+
+ /**
+ * This structure is used to store the currently active objects (ViewRootImpls or
+ * WallpaperService.Engines) in the process. Each of these objects sends a request to
+ * AnimationHandler when it goes into the background (request to pause) or foreground
+ * (request to resume). Because all animators are managed by AnimationHandler on the same
+ * thread, it should only ever pause animators when *all* requestors are in the background.
+ * This list tracks the background/foreground state of all requestors and only ever
+ * pauses animators when all items are in the background (false). To simplify, we only ever
+ * store visible (foreground) requestors; if the set size reaches zero, there are no
+ * objects in the foreground and it is time to pause animators.
+ */
+ private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>();
+
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
@@ -68,6 +93,89 @@ public class AnimationHandler {
return sAnimatorHandler.get();
}
+
+ /**
+ * This is called when a window goes away. We should remove
+ * it from the requestors list to ensure that we are counting requests correctly and not
+ * tracking obsolete+enabled requestors.
+ */
+ public static void removeRequestor(Object requestor) {
+ getInstance().removeRequestorImpl(requestor);
+ }
+
+ private void removeRequestorImpl(Object requestor) {
+ // Also request disablement, in case that requestor was the sole object keeping
+ // animators un-paused
+ requestAnimatorsEnabled(false, requestor);
+ mAnimatorRequestors.remove(requestor);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "removeRequestorImpl for " + requestor);
+ for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
+ Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+ }
+ }
+ }
+
+ /**
+ * This method is called from ViewRootImpl or WallpaperService when either a window is no
+ * longer visible (enable == false) or when a window becomes visible (enable == true).
+ * If animators are not properly disabled when activities are backgrounded, it can lead to
+ * unnecessary processing, particularly for infinite animators, as the system will continue
+ * to pulse timing events even though the results are not visible. As a workaround, we
+ * pause all un-paused infinite animators, and resume them when any window in the process
+ * becomes visible.
+ */
+ public static void requestAnimatorsEnabled(boolean enable, Object requestor) {
+ getInstance().requestAnimatorsEnabledImpl(enable, requestor);
+ }
+
+ private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
+ boolean wasEmpty = mAnimatorRequestors.isEmpty();
+ if (enable) {
+ mAnimatorRequestors.add(requestor);
+ } else {
+ mAnimatorRequestors.remove(requestor);
+ }
+ boolean isEmpty = mAnimatorRequestors.isEmpty();
+ if (wasEmpty != isEmpty) {
+ // only paused/resume animators if there was a visibility change
+ if (!isEmpty) {
+ // If any requestors are enabled, resume currently paused animators
+ Choreographer.getInstance().removeFrameCallback(mPauser);
+ for (int i = mPausedAnimators.size() - 1; i >= 0; --i) {
+ mPausedAnimators.get(i).resume();
+ }
+ mPausedAnimators.clear();
+ } else {
+ // Wait before pausing to avoid thrashing animator state for temporary backgrounding
+ Choreographer.getInstance().postFrameCallbackDelayed(mPauser,
+ Animator.getBackgroundPauseDelay());
+ }
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor);
+ for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
+ Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+ }
+ }
+ }
+
+ private Choreographer.FrameCallback mPauser = frameTimeNanos -> {
+ if (mAnimatorRequestors.size() > 0) {
+ // something enabled animators since this callback was scheduled - bail
+ return;
+ }
+ for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
+ Animator animator = ((Animator) mAnimationCallbacks.get(i));
+ if (animator != null
+ && animator.getTotalDuration() == Animator.DURATION_INFINITE
+ && !animator.isPaused()) {
+ mPausedAnimators.add(animator);
+ animator.pause();
+ }
+ }
+ };
+
/**
* By default, the Choreographer is used to provide timing for frame callbacks. A custom
* provider can be used here to provide different timing pulse.