520 lines
20 KiB
Java
520 lines
20 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2015 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.animation;
|
||
|
|
||
|
import android.annotation.Nullable;
|
||
|
import android.os.SystemClock;
|
||
|
import android.os.SystemProperties;
|
||
|
import android.util.ArrayMap;
|
||
|
import android.util.Log;
|
||
|
import android.view.Choreographer;
|
||
|
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.util.ArrayList;
|
||
|
|
||
|
/**
|
||
|
* This custom, static handler handles the timing pulse that is shared by all active
|
||
|
* ValueAnimators. This approach ensures that the setting of animation values will happen on the
|
||
|
* same thread that animations start on, and that all animations will share the same times for
|
||
|
* calculating their values, which makes synchronizing animations possible.
|
||
|
*
|
||
|
* The handler uses the Choreographer by default for doing periodic callbacks. A custom
|
||
|
* AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
|
||
|
* may be independent of UI frame update. This could be useful in testing.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public class AnimationHandler {
|
||
|
|
||
|
private static final String TAG = "AnimationHandler";
|
||
|
private static final boolean LOCAL_LOGV = false;
|
||
|
|
||
|
/**
|
||
|
* Internal per-thread collections used to avoid set collisions as animations start and end
|
||
|
* while being processed.
|
||
|
*/
|
||
|
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
|
||
|
new ArrayMap<>();
|
||
|
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
|
||
|
new ArrayList<>();
|
||
|
private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
|
||
|
new ArrayList<>();
|
||
|
private AnimationFrameCallbackProvider mProvider;
|
||
|
|
||
|
// Static flag which allows the pausing behavior to be globally disabled/enabled.
|
||
|
private static boolean sAnimatorPausingEnabled = isPauseBgAnimationsEnabledInSystemProperties();
|
||
|
|
||
|
// Static flag which prevents the system property from overriding sAnimatorPausingEnabled field.
|
||
|
private static boolean sOverrideAnimatorPausingSystemProperty = false;
|
||
|
|
||
|
/**
|
||
|
* 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 ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
|
||
|
|
||
|
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
|
||
|
@Override
|
||
|
public void doFrame(long frameTimeNanos) {
|
||
|
doAnimationFrame(getProvider().getFrameTime());
|
||
|
if (mAnimationCallbacks.size() > 0) {
|
||
|
getProvider().postFrameCallback(this);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
|
||
|
private static AnimationHandler sTestHandler = null;
|
||
|
private boolean mListDirty = false;
|
||
|
|
||
|
public static AnimationHandler getInstance() {
|
||
|
if (sTestHandler != null) {
|
||
|
return sTestHandler;
|
||
|
}
|
||
|
if (sAnimatorHandler.get() == null) {
|
||
|
sAnimatorHandler.set(new AnimationHandler());
|
||
|
}
|
||
|
return sAnimatorHandler.get();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets an instance that will be returned by {@link #getInstance()} on every thread.
|
||
|
* @return the previously active test handler, if any.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static @Nullable AnimationHandler setTestHandler(@Nullable AnimationHandler handler) {
|
||
|
AnimationHandler oldHandler = sTestHandler;
|
||
|
sTestHandler = handler;
|
||
|
return oldHandler;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* System property that controls the behavior of pausing infinite animators when an app
|
||
|
* is moved to the background.
|
||
|
*
|
||
|
* @return the value of 'framework.pause_bg_animations.enabled' system property
|
||
|
*/
|
||
|
private static boolean isPauseBgAnimationsEnabledInSystemProperties() {
|
||
|
if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled;
|
||
|
return SystemProperties
|
||
|
.getBoolean("framework.pause_bg_animations.enabled", true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disable the default behavior of pausing infinite animators when
|
||
|
* apps go into the background.
|
||
|
*
|
||
|
* @param enable Enable (default behavior) or disable background pausing behavior.
|
||
|
*/
|
||
|
public static void setAnimatorPausingEnabled(boolean enable) {
|
||
|
sAnimatorPausingEnabled = enable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prevents the setAnimatorPausingEnabled behavior from being overridden
|
||
|
* by the 'framework.pause_bg_animations.enabled' system property value.
|
||
|
*
|
||
|
* This is for testing purposes only.
|
||
|
*
|
||
|
* @param enable Enable or disable (default behavior) overriding the system
|
||
|
* property.
|
||
|
*/
|
||
|
public static void setOverrideAnimatorPausingSystemProperty(boolean enable) {
|
||
|
sOverrideAnimatorPausingSystemProperty = enable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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().requestAnimatorsEnabledImpl(false, requestor);
|
||
|
if (LOCAL_LOGV) {
|
||
|
Log.v(TAG, "removeRequestor for " + requestor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
|
||
|
synchronized (mAnimatorRequestors) {
|
||
|
// Only store WeakRef objects to avoid leaks
|
||
|
if (enable) {
|
||
|
// First, check whether such a reference is already on the list
|
||
|
WeakReference<Object> weakRef = null;
|
||
|
for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
|
||
|
WeakReference<Object> ref = mAnimatorRequestors.get(i);
|
||
|
Object referent = ref.get();
|
||
|
if (referent == requestor) {
|
||
|
weakRef = ref;
|
||
|
} else if (referent == null) {
|
||
|
// Remove any reference that has been cleared
|
||
|
mAnimatorRequestors.remove(i);
|
||
|
}
|
||
|
}
|
||
|
if (weakRef == null) {
|
||
|
weakRef = new WeakReference<>(requestor);
|
||
|
mAnimatorRequestors.add(weakRef);
|
||
|
}
|
||
|
} else {
|
||
|
for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
|
||
|
WeakReference<Object> ref = mAnimatorRequestors.get(i);
|
||
|
Object referent = ref.get();
|
||
|
if (referent == requestor || referent == null) {
|
||
|
// remove requested item or item that has been cleared
|
||
|
mAnimatorRequestors.remove(i);
|
||
|
}
|
||
|
}
|
||
|
// If a reference to the requestor wasn't in the list, nothing to remove
|
||
|
}
|
||
|
}
|
||
|
if (!sAnimatorPausingEnabled) {
|
||
|
// Resume any animators that have been paused in the meantime, otherwise noop
|
||
|
// Leave logic above so that if pausing gets re-enabled, the state of the requestors
|
||
|
// list is valid
|
||
|
resumeAnimators();
|
||
|
return;
|
||
|
}
|
||
|
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
|
||
|
resumeAnimators();
|
||
|
} 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
|
||
|
+ " with pauseDelay of " + Animator.getBackgroundPauseDelay());
|
||
|
for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
|
||
|
Log.v(TAG, "animatorRequestors " + i + " = "
|
||
|
+ mAnimatorRequestors.get(i) + " with referent "
|
||
|
+ mAnimatorRequestors.get(i).get());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void resumeAnimators() {
|
||
|
Choreographer.getInstance().removeFrameCallback(mPauser);
|
||
|
for (int i = mPausedAnimators.size() - 1; i >= 0; --i) {
|
||
|
mPausedAnimators.get(i).resume();
|
||
|
}
|
||
|
mPausedAnimators.clear();
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
AnimationFrameCallback callback = mAnimationCallbacks.get(i);
|
||
|
if (callback instanceof Animator) {
|
||
|
Animator animator = ((Animator) callback);
|
||
|
if (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.
|
||
|
*/
|
||
|
public void setProvider(AnimationFrameCallbackProvider provider) {
|
||
|
if (provider == null) {
|
||
|
mProvider = new MyFrameCallbackProvider();
|
||
|
} else {
|
||
|
mProvider = provider;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private AnimationFrameCallbackProvider getProvider() {
|
||
|
if (mProvider == null) {
|
||
|
mProvider = new MyFrameCallbackProvider();
|
||
|
}
|
||
|
return mProvider;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register to get a callback on the next frame after the delay.
|
||
|
*/
|
||
|
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
|
||
|
if (mAnimationCallbacks.size() == 0) {
|
||
|
getProvider().postFrameCallback(mFrameCallback);
|
||
|
}
|
||
|
if (!mAnimationCallbacks.contains(callback)) {
|
||
|
mAnimationCallbacks.add(callback);
|
||
|
}
|
||
|
|
||
|
if (delay > 0) {
|
||
|
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register to get a one shot callback for frame commit timing. Frame commit timing is the
|
||
|
* time *after* traversals are done, as opposed to the animation frame timing, which is
|
||
|
* before any traversals. This timing can be used to adjust the start time of an animation
|
||
|
* when expensive traversals create big delta between the animation frame timing and the time
|
||
|
* that animation is first shown on screen.
|
||
|
*
|
||
|
* Note this should only be called when the animation has already registered to receive
|
||
|
* animation frame callbacks. This callback will be guaranteed to happen *after* the next
|
||
|
* animation frame callback.
|
||
|
*/
|
||
|
public void addOneShotCommitCallback(final AnimationFrameCallback callback) {
|
||
|
if (!mCommitCallbacks.contains(callback)) {
|
||
|
mCommitCallbacks.add(callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes the given callback from the list, so it will no longer be called for frame related
|
||
|
* timing.
|
||
|
*/
|
||
|
public void removeCallback(AnimationFrameCallback callback) {
|
||
|
mCommitCallbacks.remove(callback);
|
||
|
mDelayedCallbackStartTime.remove(callback);
|
||
|
int id = mAnimationCallbacks.indexOf(callback);
|
||
|
if (id >= 0) {
|
||
|
mAnimationCallbacks.set(id, null);
|
||
|
mListDirty = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void doAnimationFrame(long frameTime) {
|
||
|
long currentTime = SystemClock.uptimeMillis();
|
||
|
final int size = mAnimationCallbacks.size();
|
||
|
for (int i = 0; i < size; i++) {
|
||
|
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
|
||
|
if (callback == null) {
|
||
|
continue;
|
||
|
}
|
||
|
if (isCallbackDue(callback, currentTime)) {
|
||
|
callback.doAnimationFrame(frameTime);
|
||
|
if (mCommitCallbacks.contains(callback)) {
|
||
|
getProvider().postCommitCallback(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
commitAnimationFrame(callback, getProvider().getFrameTime());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
cleanUpList();
|
||
|
}
|
||
|
|
||
|
private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
|
||
|
if (!mDelayedCallbackStartTime.containsKey(callback) &&
|
||
|
mCommitCallbacks.contains(callback)) {
|
||
|
callback.commitAnimationFrame(frameTime);
|
||
|
mCommitCallbacks.remove(callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
|
||
|
* so that they can start getting frame callbacks.
|
||
|
*
|
||
|
* @return true if they have passed the initial delay or have no delay, false otherwise.
|
||
|
*/
|
||
|
private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
|
||
|
Long startTime = mDelayedCallbackStartTime.get(callback);
|
||
|
if (startTime == null) {
|
||
|
return true;
|
||
|
}
|
||
|
if (startTime < currentTime) {
|
||
|
mDelayedCallbackStartTime.remove(callback);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the number of callbacks that have registered for frame callbacks.
|
||
|
*/
|
||
|
public static int getAnimationCount() {
|
||
|
AnimationHandler handler = sTestHandler;
|
||
|
if (handler == null) {
|
||
|
handler = sAnimatorHandler.get();
|
||
|
}
|
||
|
if (handler == null) {
|
||
|
return 0;
|
||
|
}
|
||
|
return handler.getCallbackSize();
|
||
|
}
|
||
|
|
||
|
public static void setFrameDelay(long delay) {
|
||
|
getInstance().getProvider().setFrameDelay(delay);
|
||
|
}
|
||
|
|
||
|
public static long getFrameDelay() {
|
||
|
return getInstance().getProvider().getFrameDelay();
|
||
|
}
|
||
|
|
||
|
void autoCancelBasedOn(ObjectAnimator objectAnimator) {
|
||
|
for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
|
||
|
AnimationFrameCallback cb = mAnimationCallbacks.get(i);
|
||
|
if (cb == null) {
|
||
|
continue;
|
||
|
}
|
||
|
if (objectAnimator.shouldAutoCancel(cb)) {
|
||
|
((Animator) mAnimationCallbacks.get(i)).cancel();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void cleanUpList() {
|
||
|
if (mListDirty) {
|
||
|
for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
|
||
|
if (mAnimationCallbacks.get(i) == null) {
|
||
|
mAnimationCallbacks.remove(i);
|
||
|
}
|
||
|
}
|
||
|
mListDirty = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int getCallbackSize() {
|
||
|
int count = 0;
|
||
|
int size = mAnimationCallbacks.size();
|
||
|
for (int i = size - 1; i >= 0; i--) {
|
||
|
if (mAnimationCallbacks.get(i) != null) {
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Default provider of timing pulse that uses Choreographer for frame callbacks.
|
||
|
*/
|
||
|
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
|
||
|
|
||
|
final Choreographer mChoreographer = Choreographer.getInstance();
|
||
|
|
||
|
@Override
|
||
|
public void postFrameCallback(Choreographer.FrameCallback callback) {
|
||
|
mChoreographer.postFrameCallback(callback);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void postCommitCallback(Runnable runnable) {
|
||
|
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public long getFrameTime() {
|
||
|
return mChoreographer.getFrameTime();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public long getFrameDelay() {
|
||
|
return Choreographer.getFrameDelay();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setFrameDelay(long delay) {
|
||
|
Choreographer.setFrameDelay(delay);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Callbacks that receives notifications for animation timing and frame commit timing.
|
||
|
* @hide
|
||
|
*/
|
||
|
public interface AnimationFrameCallback {
|
||
|
/**
|
||
|
* Run animation based on the frame time.
|
||
|
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
|
||
|
* base.
|
||
|
* @return if the animation has finished.
|
||
|
*/
|
||
|
boolean doAnimationFrame(long frameTime);
|
||
|
|
||
|
/**
|
||
|
* This notifies the callback of frame commit time. Frame commit time is the time after
|
||
|
* traversals happen, as opposed to the normal animation frame time that is before
|
||
|
* traversals. This is used to compensate expensive traversals that happen as the
|
||
|
* animation starts. When traversals take a long time to complete, the rendering of the
|
||
|
* initial frame will be delayed (by a long time). But since the startTime of the
|
||
|
* animation is set before the traversal, by the time of next frame, a lot of time would
|
||
|
* have passed since startTime was set, the animation will consequently skip a few frames
|
||
|
* to respect the new frameTime. By having the commit time, we can adjust the start time to
|
||
|
* when the first frame was drawn (after any expensive traversals) so that no frames
|
||
|
* will be skipped.
|
||
|
*
|
||
|
* @param frameTime The frame time after traversals happen, if any, in the
|
||
|
* {@link SystemClock#uptimeMillis()} time base.
|
||
|
*/
|
||
|
void commitAnimationFrame(long frameTime);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The intention for having this interface is to increase the testability of ValueAnimator.
|
||
|
* Specifically, we can have a custom implementation of the interface below and provide
|
||
|
* timing pulse without using Choreographer. That way we could use any arbitrary interval for
|
||
|
* our timing pulse in the tests.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public interface AnimationFrameCallbackProvider {
|
||
|
void postFrameCallback(Choreographer.FrameCallback callback);
|
||
|
void postCommitCallback(Runnable runnable);
|
||
|
long getFrameTime();
|
||
|
long getFrameDelay();
|
||
|
void setFrameDelay(long delay);
|
||
|
}
|
||
|
}
|