Merge "Fine tune animation parameters for IME animations" into rvc-dev

This commit is contained in:
Jorim Jaggi
2020-03-20 11:11:04 +00:00
committed by Android (Google) Code Review
5 changed files with 118 additions and 77 deletions

View File

@@ -68,7 +68,6 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
private final @InsetsType int mTypes; private final @InsetsType int mTypes;
private final InsetsAnimationControlCallbacks mController; private final InsetsAnimationControlCallbacks mController;
private final WindowInsetsAnimation mAnimation; private final WindowInsetsAnimation mAnimation;
private final boolean mFade;
private Insets mCurrentInsets; private Insets mCurrentInsets;
private Insets mPendingInsets; private Insets mPendingInsets;
private float mPendingFraction; private float mPendingFraction;
@@ -83,11 +82,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
InsetsState state, WindowInsetsAnimationControlListener listener, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types, @InsetsType int types,
InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
boolean fade, @AnimationType int animationType) { @AnimationType int animationType) {
mControls = controls; mControls = controls;
mListener = listener; mListener = listener;
mTypes = types; mTypes = types;
mFade = fade;
mController = controller; mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */); mInitialInsetsState = new InsetsState(state, true /* copySources */);
mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
@@ -299,8 +297,6 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
// If the system is controlling the insets source, the leash can be null. // If the system is controlling the insets source, the leash can be null.
if (leash != null) { if (leash != null) {
// TODO: use a better interpolation for fade.
alpha = mFade ? ((float) inset / maxInset * 0.3f + 0.7f) : alpha;
SurfaceParams params = new SurfaceParams.Builder(leash) SurfaceParams params = new SurfaceParams.Builder(leash)
.withAlpha(side == ISIDE_FLOATING ? 1 : alpha) .withAlpha(side == ISIDE_FLOATING ? 1 : alpha)
.withMatrix(mTmpMatrix) .withMatrix(mTmpMatrix)

View File

@@ -88,11 +88,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
InsetsState state, WindowInsetsAnimationControlListener listener, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types, @InsetsType int types,
InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
boolean fade, @AnimationType int animationType, Handler mainThreadHandler) { @AnimationType int animationType, Handler mainThreadHandler) {
mMainThreadHandler = mainThreadHandler; mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller; mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener, mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener,
types, mCallbacks, durationMs, interpolator, fade, animationType); types, mCallbacks, durationMs, interpolator, animationType);
InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types)); InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types));
} }

View File

@@ -27,7 +27,9 @@ import android.animation.AnimationHandler;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator; import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -36,6 +38,7 @@ import android.graphics.Rect;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.Handler; import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.renderscript.Sampler.Value;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@@ -50,6 +53,7 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator; import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -72,9 +76,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private static final int ANIMATION_DURATION_SHOW_MS = 275; private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340; private static final int ANIMATION_DURATION_HIDE_MS = 340;
private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
private static final int PENDING_CONTROL_TIMEOUT_MS = 2000; private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); public static final Interpolator SYSTEM_BARS_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 0.2f, 1f);
private static final Interpolator SYNC_IME_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
new PathInterpolator(0, 0, 0.2f, 1f);
private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
/** /**
* Layout mode during insets animation: The views should be laid out as if the changing inset * Layout mode during insets animation: The views should be laid out as if the changing inset
@@ -138,27 +153,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
(int) (startValue.right + fraction * (endValue.right - startValue.right)), (int) (startValue.right + fraction * (endValue.right - startValue.right)),
(int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
/**
* Linear animation property
*/
private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
InsetsProperty() {
super(Insets.class, "Insets");
}
@Override
public Insets get(WindowInsetsAnimationController object) {
return object.getCurrentInsets();
}
@Override
public void set(WindowInsetsAnimationController controller, Insets value) {
controller.setInsetsAndAlpha(
value, 1f /* alpha */, (((InternalAnimationControlListener)
((InsetsAnimationControlImpl) controller).getListener())
.getRawFraction()));
}
}
/** /**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets. * animate insets.
@@ -167,9 +161,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
implements WindowInsetsAnimationControlListener { implements WindowInsetsAnimationControlListener {
private WindowInsetsAnimationController mController; private WindowInsetsAnimationController mController;
private ObjectAnimator mAnimator; private ValueAnimator mAnimator;
private final boolean mShow; private final boolean mShow;
private final boolean mUseSfVsync; private final boolean mHasAnimationCallbacks;
private final @InsetsType int mRequestedTypes;
private final long mDurationMs;
private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() { new ThreadLocal<AnimationHandler>() {
@@ -181,24 +177,40 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
} }
}; };
public InternalAnimationControlListener(boolean show, boolean useSfVsync) { public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
int requestedTypes) {
mShow = show; mShow = show;
mUseSfVsync = useSfVsync; mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
mDurationMs = calculateDurationMs();
} }
@Override @Override
public void onReady(WindowInsetsAnimationController controller, int types) { public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller; mController = controller;
mAnimator = ObjectAnimator.ofObject( mAnimator = ValueAnimator.ofFloat(0f, 1f);
controller, mAnimator.setDuration(mDurationMs);
new InsetsProperty(), mAnimator.setInterpolator(new LinearInterpolator());
sEvaluator, Insets start = mShow
mShow ? controller.getHiddenStateInsets() : controller.getShownStateInsets(), ? controller.getHiddenStateInsets()
mShow ? controller.getShownStateInsets() : controller.getHiddenStateInsets() : controller.getShownStateInsets();
); Insets end = mShow
mAnimator.setDuration(getDurationMs()); ? controller.getShownStateInsets()
mAnimator.setInterpolator(INTERPOLATOR); : controller.getHiddenStateInsets();
Interpolator insetsInterpolator = getInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
float alphaFraction = mShow
? rawFraction
: 1 - rawFraction;
float insetsFraction = insetsInterpolator.getInterpolation(rawFraction);
controller.setInsetsAndAlpha(
sEvaluator.evaluate(insetsFraction, start, end),
alphaInterpolator.getInterpolation(alphaFraction),
rawFraction);
});
mAnimator.addListener(new AnimatorListenerAdapter() { mAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
@@ -206,7 +218,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
onAnimationFinish(); onAnimationFinish();
} }
}); });
if (mUseSfVsync) { if (!mHasAnimationCallbacks) {
mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
} }
mAnimator.start(); mAnimator.start();
@@ -224,24 +236,59 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
} }
} }
protected void onAnimationFinish() { Interpolator getInterpolator() {
mController.finish(mShow); if ((mRequestedTypes & ime()) != 0) {
if (mHasAnimationCallbacks) {
return SYNC_IME_INTERPOLATOR;
} else if (mShow) {
return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
} else {
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
return SYSTEM_BARS_INTERPOLATOR;
}
} }
protected float getRawFraction() { Interpolator getAlphaInterpolator() {
return (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration(); if ((mRequestedTypes & ime()) != 0) {
if (mHasAnimationCallbacks) {
return input -> 1f;
} else if (mShow) {
// Alpha animation takes half the time with linear interpolation;
return input -> Math.min(1f, 2 * input);
} else {
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
return input -> 1f;
}
}
protected void onAnimationFinish() {
mController.finish(mShow);
} }
/** /**
* To get the animation duration in MS. * To get the animation duration in MS.
*/ */
public long getDurationMs() { public long getDurationMs() {
if (mAnimator != null) { return mDurationMs;
return mAnimator.getDuration();
} }
private long calculateDurationMs() {
if ((mRequestedTypes & ime()) != 0) {
if (mHasAnimationCallbacks) {
return ANIMATION_DURATION_SYNC_IME_MS;
} else {
return ANIMATION_DURATION_UNSYNC_IME_MS;
}
} else {
return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS; return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
} }
} }
}
/** /**
* Represents a running animation * Represents a running animation
@@ -525,7 +572,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
pendingRequest.types, pendingRequest.cancellationSignal, pendingRequest.types, pendingRequest.cancellationSignal,
pendingRequest.listener, mFrame, pendingRequest.listener, mFrame,
true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
false /* fade */, pendingRequest.animationType, pendingRequest.animationType,
pendingRequest.layoutInsetsDuringAnimation, pendingRequest.layoutInsetsDuringAnimation,
pendingRequest.useInsetsAnimationThread); pendingRequest.useInsetsAnimationThread);
return; return;
@@ -590,9 +637,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
listener.onCancelled(null); listener.onCancelled(null);
return; return;
} }
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, false /* fade */, animationType, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
getLayoutInsetsDuringAnimationMode(types),
false /* useInsetsAnimationThread */); false /* useInsetsAnimationThread */);
} }
@@ -606,7 +653,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void controlAnimationUnchecked(@InsetsType int types, private void controlAnimationUnchecked(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal, @Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
long durationMs, Interpolator interpolator, boolean fade, long durationMs, Interpolator interpolator,
@AnimationType int animationType, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread) { boolean useInsetsAnimationThread) {
@@ -652,10 +699,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InsetsAnimationControlRunner runner = useInsetsAnimationThread final InsetsAnimationControlRunner runner = useInsetsAnimationThread
? new InsetsAnimationThreadControlRunner(controls, ? new InsetsAnimationThreadControlRunner(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator, fade, frame, mState, listener, typesReady, this, durationMs, interpolator,
animationType, mViewRoot.mHandler) animationType, mViewRoot.mHandler)
: new InsetsAnimationControlImpl(controls, : new InsetsAnimationControlImpl(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator, fade, frame, mState, listener, typesReady, this, durationMs, interpolator,
animationType); animationType);
mRunningAnimations.add(new RunningAnimation(runner, animationType)); mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (cancellationSignal != null) { if (cancellationSignal != null) {
@@ -921,25 +968,26 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
return; return;
} }
boolean useInsetsAnimationThread = canUseInsetsAnimationThread(); boolean hasAnimationCallbacks = hasAnimationCallbacks();
final InternalAnimationControlListener listener = final InternalAnimationControlListener listener =
new InternalAnimationControlListener(show, useInsetsAnimationThread); new InternalAnimationControlListener(show, hasAnimationCallbacks, types);
// Show/hide animations always need to be relative to the display frame, in order that shown // Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct. // and hidden state insets are correct.
controlAnimationUnchecked( controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, mState.getDisplayFrame(), fromIme, types, null /* cancellationSignal */, listener, mState.getDisplayFrame(), fromIme,
listener.getDurationMs(), listener.getDurationMs(), listener.getInterpolator(),
INTERPOLATOR, true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, !hasAnimationCallbacks /* useInsetsAnimationThread */);
useInsetsAnimationThread);
} }
private boolean canUseInsetsAnimationThread() { private boolean hasAnimationCallbacks() {
if (mViewRoot.mView == null) { if (mViewRoot.mView == null) {
return true; return false;
} }
return !mViewRoot.mView.hasWindowInsetsAnimationCallback(); return mViewRoot.mView.hasWindowInsetsAnimationCallback();
} }
private void hideDirectly( private void hideDirectly(

View File

@@ -20,7 +20,6 @@ import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -44,8 +43,6 @@ import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator;
import android.view.test.InsetsModeSession; import android.view.test.InsetsModeSession;
import androidx.test.runner.AndroidJUnit4;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@@ -57,6 +54,8 @@ import org.mockito.MockitoAnnotations;
import java.util.List; import java.util.List;
import androidx.test.runner.AndroidJUnit4;
/** /**
* Tests for {@link InsetsAnimationControlImpl}. * Tests for {@link InsetsAnimationControlImpl}.
* *
@@ -124,7 +123,7 @@ public class InsetsAnimationControlImplTest {
mController = new InsetsAnimationControlImpl(controls, mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */, new LinearInterpolator(), mMockController, 10 /* durationMs */, new LinearInterpolator(),
false /* fade */, 0 /* animationType */); 0 /* animationType */);
} }
@Test @Test

View File

@@ -22,8 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
@@ -299,7 +297,7 @@ class InsetsPolicy {
private void controlAnimationUnchecked(int typesReady, private void controlAnimationUnchecked(int typesReady,
SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) { SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
InsetsPolicyAnimationControlListener listener = InsetsPolicyAnimationControlListener listener =
new InsetsPolicyAnimationControlListener(show, callback); new InsetsPolicyAnimationControlListener(show, callback, typesReady);
listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show); listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
} }
@@ -328,8 +326,8 @@ class InsetsPolicy {
Runnable mFinishCallback; Runnable mFinishCallback;
InsetsPolicyAnimationControlCallbacks mControlCallbacks; InsetsPolicyAnimationControlCallbacks mControlCallbacks;
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback) { InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, true /* useSfVsync */); super(show, false /* hasCallbacks */, types);
mFinishCallback = finishCallback; mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
} }
@@ -361,7 +359,7 @@ class InsetsPolicy {
mAnimationControl = new InsetsAnimationControlImpl(controls, mAnimationControl = new InsetsAnimationControlImpl(controls,
mFocusedWin.getDisplayContent().getBounds(), getState(), mFocusedWin.getDisplayContent().getBounds(), getState(),
mListener, typesReady, this, mListener.getDurationMs(), mListener, typesReady, this, mListener.getDurationMs(),
InsetsController.INTERPOLATOR, true, InsetsController.SYSTEM_BARS_INTERPOLATOR,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE); show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE);
SurfaceAnimationThread.getHandler().post( SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady)); () -> mListener.onReady(mAnimationControl, typesReady));