summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chet Haase <chet@google.com> 2022-05-16 22:57:06 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-05-16 22:57:06 +0000
commitb68f279eeff7115538c0f295da4ecd98b97c98c7 (patch)
tree830653a0b04655dee08c4db4140c2556dafe4860
parentfccd45f4a5e63be4695789c2f60b52a1302acb05 (diff)
parent298ab29152a4d2f774e3ecd56e0231d98efe0da6 (diff)
Merge "Pause animators when app is not visible"
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/animation/AnimationHandler.java110
-rw-r--r--core/java/android/animation/Animator.java29
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java6
-rw-r--r--core/java/android/view/ViewRootImpl.java5
5 files changed, 154 insertions, 1 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 21220c589bbf..5c532a0aaaf5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -96,6 +96,11 @@ package android.accessibilityservice {
package android.animation {
+ public abstract class Animator implements java.lang.Cloneable {
+ method public static long getBackgroundPauseDelay();
+ method public static void setBackgroundPauseDelay(long);
+ }
+
public class ValueAnimator extends android.animation.Animator {
method @MainThread public static void setDurationScale(@FloatRange(from=0) float);
}
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.
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a8ff36aae098..9e55d359b416 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,34 @@ 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;
+ }
+
+ /**
* 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 2bdc581d8a73..de3bad3c25c8 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;
@@ -1369,6 +1370,8 @@ public final class ViewRootImpl implements ViewParent,
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
+
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}
}
@@ -1714,6 +1717,7 @@ public final class ViewRootImpl implements ViewParent,
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}
@@ -8501,6 +8505,7 @@ public final class ViewRootImpl implements ViewParent,
mInsetsController.onControlsChanged(null);
mAdded = false;
+ AnimationHandler.removeRequestor(this);
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}