diff options
| author | 2022-05-16 22:57:06 +0000 | |
|---|---|---|
| committer | 2022-05-16 22:57:06 +0000 | |
| commit | b68f279eeff7115538c0f295da4ecd98b97c98c7 (patch) | |
| tree | 830653a0b04655dee08c4db4140c2556dafe4860 | |
| parent | fccd45f4a5e63be4695789c2f60b52a1302acb05 (diff) | |
| parent | 298ab29152a4d2f774e3ecd56e0231d98efe0da6 (diff) | |
Merge "Pause animators when app is not visible"
| -rw-r--r-- | core/api/test-current.txt | 5 | ||||
| -rw-r--r-- | core/java/android/animation/AnimationHandler.java | 110 | ||||
| -rw-r--r-- | core/java/android/animation/Animator.java | 29 | ||||
| -rw-r--r-- | core/java/android/service/wallpaper/WallpaperService.java | 6 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 5 |
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); } |