Pause animators when app is not visible am: b99cc5b4c8 am: 4551f51dcd

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18528824

Change-Id: I1950e00e4cac979a360f6c5e49164c66a8d48933
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chet Haase
2022-06-07 20:28:21 +00:00
committed by Automerger Merge Worker
5 changed files with 154 additions and 1 deletions

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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;
@@ -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

View File

@@ -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) {

View File

@@ -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);
}