Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6541ab7 authored by Chet Haase's avatar Chet Haase Committed by Automerger Merge Worker
Browse files

Pause animators when app is not visible am: b99cc5b4 am: f2e66fa4

parents 9cca92db f2e66fa4
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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);
  }
+109 −1
Original line number Diff line number Diff line
@@ -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.
+29 −0
Original line number Diff line number Diff line
@@ -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
+6 −0
Original line number Diff line number Diff line
@@ -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);
            }
        }

@@ -1545,6 +1548,7 @@ public abstract class WallpaperService extends Service {
                        if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update");
                        freeze();
                    }
                    AnimationHandler.requestAnimatorsEnabled(visible, this);
                }
            }
        }
@@ -2073,6 +2077,8 @@ public abstract class WallpaperService extends Service {
                return;
            }

            AnimationHandler.removeRequestor(this);

            mDestroyed = true;

            if (mIWallpaperEngine.mDisplayManager != null) {
+5 −0
Original line number Diff line number Diff line
@@ -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;
@@ -1366,6 +1367,8 @@ public final class ViewRootImpl implements ViewParent,
                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;

                AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
            }
        }
    }
@@ -1711,6 +1714,7 @@ public final class ViewRootImpl implements ViewParent,
            if (!mAppVisible) {
                WindowManagerGlobal.trimForeground();
            }
            AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
        }
    }

@@ -8495,6 +8499,7 @@ public final class ViewRootImpl implements ViewParent,
            mInsetsController.onControlsChanged(null);

            mAdded = false;
            AnimationHandler.removeRequestor(this);
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }