Loading core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading core/java/android/animation/AnimationHandler.java +109 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<>(); Loading @@ -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) { Loading @@ -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. Loading core/java/android/animation/Animator.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -63,6 +64,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 Loading core/java/android/service/wallpaper/WallpaperService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1516,6 +1517,8 @@ public abstract class WallpaperService extends Service { mVisible = visible; reportVisibility(); if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } else { AnimationHandler.requestAnimatorsEnabled(visible, this); } } Loading Loading @@ -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); } } } Loading Loading @@ -2072,6 +2076,8 @@ public abstract class WallpaperService extends Service { return; } AnimationHandler.removeRequestor(this); mDestroyed = true; if (mIWallpaperEngine.mDisplayManager != null) { Loading core/java/android/view/ViewRootImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1369,6 +1370,8 @@ public final class ViewRootImpl implements ViewParent, mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); } } } Loading Loading @@ -1714,6 +1717,7 @@ public final class ViewRootImpl implements ViewParent, if (!mAppVisible) { WindowManagerGlobal.trimForeground(); } AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); } } Loading Loading @@ -8501,6 +8505,7 @@ public final class ViewRootImpl implements ViewParent, mInsetsController.onControlsChanged(null); mAdded = false; AnimationHandler.removeRequestor(this); } WindowManagerGlobal.getInstance().doRemoveView(this); } Loading Loading
core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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); } Loading
core/java/android/animation/AnimationHandler.java +109 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<>(); Loading @@ -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) { Loading @@ -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. Loading
core/java/android/animation/Animator.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -63,6 +64,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 Loading
core/java/android/service/wallpaper/WallpaperService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1516,6 +1517,8 @@ public abstract class WallpaperService extends Service { mVisible = visible; reportVisibility(); if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } else { AnimationHandler.requestAnimatorsEnabled(visible, this); } } Loading Loading @@ -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); } } } Loading Loading @@ -2072,6 +2076,8 @@ public abstract class WallpaperService extends Service { return; } AnimationHandler.removeRequestor(this); mDestroyed = true; if (mIWallpaperEngine.mDisplayManager != null) { Loading
core/java/android/view/ViewRootImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -1369,6 +1370,8 @@ public final class ViewRootImpl implements ViewParent, mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); } } } Loading Loading @@ -1714,6 +1717,7 @@ public final class ViewRootImpl implements ViewParent, if (!mAppVisible) { WindowManagerGlobal.trimForeground(); } AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); } } Loading Loading @@ -8501,6 +8505,7 @@ public final class ViewRootImpl implements ViewParent, mInsetsController.onControlsChanged(null); mAdded = false; AnimationHandler.removeRequestor(this); } WindowManagerGlobal.getInstance().doRemoveView(this); } Loading