summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorSteven Terrell <steventerrell@google.com>2022-06-07 19:49:48 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2022-06-07 19:49:48 +0000
commita4ac1574bddcb7e0951ec46d9415c6255306f2fa (patch)
treef0ffa10607f752f3fb66aa6d47f36ebbc4aac5a0 /core/java/android
parentbb4868d65de612ecfa968956176e0b9d3286fa0f (diff)
parent063ae10a70aa27438f275e652e877897037aec83 (diff)
Merge changes from topic "stop-leaked-animators" into tm-dev
* changes: Use System Property to Control Animator Pausing Disable debug logging in AnimationHandler Allow system to disable behavior of pausing animators for bg apps Pause animators when app is not visible
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/animation/AnimationHandler.java163
-rw-r--r--core/java/android/animation/Animator.java44
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java6
-rw-r--r--core/java/android/view/ViewRootImpl.java5
4 files changed, 217 insertions, 1 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 260323fe2d10..7e814af3451d 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -17,7 +17,10 @@
package android.animation;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
import android.view.Choreographer;
import java.util.ArrayList;
@@ -35,10 +38,13 @@ import java.util.ArrayList;
* @hide
*/
public class AnimationHandler {
+
+ private static final String TAG = "AnimationHandler";
+ private static final boolean LOCAL_LOGV = false;
+
/**
* 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 +54,32 @@ public class AnimationHandler {
new ArrayList<>();
private AnimationFrameCallbackProvider mProvider;
+ // Static flag which allows the pausing behavior to be globally disabled/enabled.
+ private static boolean sAnimatorPausingEnabled = isPauseBgAnimationsEnabledInSystemProperties();
+
+ // Static flag which prevents the system property from overriding sAnimatorPausingEnabled field.
+ private static boolean sOverrideAnimatorPausingSystemProperty = false;
+
+ /**
+ * 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) {
@@ -69,6 +101,135 @@ public class AnimationHandler {
}
/**
+ * System property that controls the behavior of pausing infinite animators when an app
+ * is moved to the background.
+ *
+ * @return the value of 'framework.pause_bg_animations.enabled' system property
+ */
+ private static boolean isPauseBgAnimationsEnabledInSystemProperties() {
+ if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled;
+ return SystemProperties
+ .getBoolean("framework.pause_bg_animations.enabled", true);
+ }
+
+ /**
+ * Disable the default behavior of pausing infinite animators when
+ * apps go into the background.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ */
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ sAnimatorPausingEnabled = enable;
+ }
+
+ /**
+ * Prevents the setAnimatorPausingEnabled behavior from being overridden
+ * by the 'framework.pause_bg_animations.enabled' system property value.
+ *
+ * This is for testing purposes only.
+ *
+ * @param enable Enable or disable (default behavior) overriding the system
+ * property.
+ */
+ public static void setOverrideAnimatorPausingSystemProperty(boolean enable) {
+ sOverrideAnimatorPausingSystemProperty = enable;
+ }
+
+ /**
+ * 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();
+ setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
+ if (enable) {
+ mAnimatorRequestors.add(requestor);
+ } else {
+ mAnimatorRequestors.remove(requestor);
+ }
+ if (!sAnimatorPausingEnabled) {
+ // Resume any animators that have been paused in the meantime, otherwise noop
+ // Leave logic above so that if pausing gets re-enabled, the state of the requestors
+ // list is valid
+ resumeAnimators();
+ return;
+ }
+ 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
+ resumeAnimators();
+ } 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 void resumeAnimators() {
+ Choreographer.getInstance().removeFrameCallback(mPauser);
+ for (int i = mPausedAnimators.size() - 1; i >= 0; --i) {
+ mPausedAnimators.get(i).resume();
+ }
+ mPausedAnimators.clear();
+ }
+
+ 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.
*/
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a8ff36aae098..a9d14df8bcf4 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -18,6 +18,7 @@ package android.animation;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
@@ -64,6 +65,49 @@ public abstract class Animator implements Cloneable {
private AnimatorConstantState mConstantState;
/**
+ * backing field for backgroundPauseDelay property. This could be simply a hardcoded
+ * value in AnimationHandler, but it is useful to be able to change the value in tests.
+ */
+ private static long sBackgroundPauseDelay = 10000;
+
+ /**
+ * Sets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setBackgroundPauseDelay(long value) {
+ sBackgroundPauseDelay = value;
+ }
+
+ /**
+ * Gets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static long getBackgroundPauseDelay() {
+ return sBackgroundPauseDelay;
+ }
+
+ /**
+ * Sets the behavior of animator pausing when apps go into the background.
+ * This is exposed as a test API for verification, but is intended for use by internal/
+ * platform code, potentially for use by a system property that could disable it
+ * system wide.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ * @hide
+ */
+ @TestApi
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ AnimationHandler.setAnimatorPausingEnabled(enable);
+ AnimationHandler.setOverrideAnimatorPausingSystemProperty(!enable);
+ }
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d598017dacaa..1e22856c1bde 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,6 +27,7 @@ import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -1516,6 +1517,8 @@ public abstract class WallpaperService extends Service {
mVisible = visible;
reportVisibility();
if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ } else {
+ AnimationHandler.requestAnimatorsEnabled(visible, this);
}
}
@@ -1544,6 +1547,7 @@ public abstract class WallpaperService extends Service {
if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update");
freeze();
}
+ AnimationHandler.requestAnimatorsEnabled(visible, this);
}
}
}
@@ -2072,6 +2076,8 @@ public abstract class WallpaperService extends Service {
return;
}
+ AnimationHandler.removeRequestor(this);
+
mDestroyed = true;
if (mIWallpaperEngine.mDisplayManager != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 127c7b7a8dc9..b755f5325eeb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -90,6 +90,7 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import android.Manifest;
+import android.animation.AnimationHandler;
import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.NonNull;
@@ -1363,6 +1364,8 @@ public final class ViewRootImpl implements ViewParent,
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
+
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}
}
@@ -1708,6 +1711,7 @@ public final class ViewRootImpl implements ViewParent,
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}
@@ -8477,6 +8481,7 @@ public final class ViewRootImpl implements ViewParent,
mInsetsController.onControlsChanged(null);
mAdded = false;
+ AnimationHandler.removeRequestor(this);
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}