Pause animators when app is not visible am: b99cc5b4c8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18528824 Change-Id: I2b041e112cfa55949a9d3f753388de3536dbd33a 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 {
|
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 {
|
public class ValueAnimator extends android.animation.Animator {
|
||||||
method @MainThread public static void setDurationScale(@FloatRange(from=0) float);
|
method @MainThread public static void setDurationScale(@FloatRange(from=0) float);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package android.animation;
|
|||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Choreographer;
|
import android.view.Choreographer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -35,10 +37,13 @@ import java.util.ArrayList;
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class AnimationHandler {
|
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
|
* Internal per-thread collections used to avoid set collisions as animations start and end
|
||||||
* while being processed.
|
* while being processed.
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
|
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
|
||||||
new ArrayMap<>();
|
new ArrayMap<>();
|
||||||
@@ -48,6 +53,26 @@ public class AnimationHandler {
|
|||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
private AnimationFrameCallbackProvider mProvider;
|
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() {
|
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void doFrame(long frameTimeNanos) {
|
public void doFrame(long frameTimeNanos) {
|
||||||
@@ -68,6 +93,89 @@ public class AnimationHandler {
|
|||||||
return sAnimatorHandler.get();
|
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
|
* By default, the Choreographer is used to provide timing for frame callbacks. A custom
|
||||||
* provider can be used here to provide different timing pulse.
|
* provider can be used here to provide different timing pulse.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package android.animation;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.TestApi;
|
||||||
import android.compat.annotation.UnsupportedAppUsage;
|
import android.compat.annotation.UnsupportedAppUsage;
|
||||||
import android.content.pm.ActivityInfo.Config;
|
import android.content.pm.ActivityInfo.Config;
|
||||||
import android.content.res.ConstantState;
|
import android.content.res.ConstantState;
|
||||||
@@ -63,6 +64,34 @@ public abstract class Animator implements Cloneable {
|
|||||||
*/
|
*/
|
||||||
private AnimatorConstantState mConstantState;
|
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
|
* 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
|
* 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.ViewRootImpl.LOCAL_LAYOUT;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
||||||
|
|
||||||
|
import android.animation.AnimationHandler;
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
@@ -1516,6 +1517,8 @@ public abstract class WallpaperService extends Service {
|
|||||||
mVisible = visible;
|
mVisible = visible;
|
||||||
reportVisibility();
|
reportVisibility();
|
||||||
if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
|
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");
|
if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update");
|
||||||
freeze();
|
freeze();
|
||||||
}
|
}
|
||||||
|
AnimationHandler.requestAnimatorsEnabled(visible, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2072,6 +2076,8 @@ public abstract class WallpaperService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimationHandler.removeRequestor(this);
|
||||||
|
|
||||||
mDestroyed = true;
|
mDestroyed = true;
|
||||||
|
|
||||||
if (mIWallpaperEngine.mDisplayManager != null) {
|
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 static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.animation.AnimationHandler;
|
||||||
import android.animation.LayoutTransition;
|
import android.animation.LayoutTransition;
|
||||||
import android.annotation.AnyThread;
|
import android.annotation.AnyThread;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
@@ -1363,6 +1364,8 @@ public final class ViewRootImpl implements ViewParent,
|
|||||||
mFirstInputStage = nativePreImeStage;
|
mFirstInputStage = nativePreImeStage;
|
||||||
mFirstPostImeInputStage = earlyPostImeStage;
|
mFirstPostImeInputStage = earlyPostImeStage;
|
||||||
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
|
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
|
||||||
|
|
||||||
|
AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1708,6 +1711,7 @@ public final class ViewRootImpl implements ViewParent,
|
|||||||
if (!mAppVisible) {
|
if (!mAppVisible) {
|
||||||
WindowManagerGlobal.trimForeground();
|
WindowManagerGlobal.trimForeground();
|
||||||
}
|
}
|
||||||
|
AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8477,6 +8481,7 @@ public final class ViewRootImpl implements ViewParent,
|
|||||||
mInsetsController.onControlsChanged(null);
|
mInsetsController.onControlsChanged(null);
|
||||||
|
|
||||||
mAdded = false;
|
mAdded = false;
|
||||||
|
AnimationHandler.removeRequestor(this);
|
||||||
}
|
}
|
||||||
WindowManagerGlobal.getInstance().doRemoveView(this);
|
WindowManagerGlobal.getInstance().doRemoveView(this);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user