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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user