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

Commit 9e223a8c authored by Tiger Huang's avatar Tiger Huang
Browse files

Invoke insets animation callbacks when resizing system bars

This gives the app a chance to react to the animation of resizing system
bars.

Fix: 191269755
Test: atest InsetsAnimationControlImplTest InsetsControllerTest
            InsetsStateTest
Change-Id: Ibf47047c131867983064bef4e9ac011daf66ea18
parent 5b89fd13
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -20,7 +20,8 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;

/**
 * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
 * Provide an interface to let InsetsAnimationControlImpl and InsetsResizeAnimationRunner call back
 * into its owner.
 * @hide
 */
public interface InsetsAnimationControlCallbacks {
@@ -34,10 +35,9 @@ public interface InsetsAnimationControlCallbacks {
     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
     * </ul>
     */
    void startAnimation(InsetsAnimationControlImpl controller,
            WindowInsetsAnimationControlListener listener, int types,
            WindowInsetsAnimation animation,
            Bounds bounds);
    <T extends InsetsAnimationControlRunner & WindowInsetsAnimationController>
    void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
            WindowInsetsAnimation animation, Bounds bounds);

    /**
     * Schedule the apply by posting the animation callback.
+6 −1
Original line number Diff line number Diff line
@@ -105,7 +105,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
    private float mCurrentAlpha = 1.0f;
    private float mPendingAlpha = 1.0f;
    @VisibleForTesting(visibility = PACKAGE)
    public boolean mReadyDispatched;
    private boolean mReadyDispatched;
    private Boolean mPerceptible;

    @VisibleForTesting
@@ -169,6 +169,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        return mHasZeroInsetsIme;
    }

    @Override
    public void setReadyDispatched(boolean dispatched) {
        mReadyDispatched = dispatched;
    }

    @Override
    public Insets getHiddenStateInsets() {
        return mHiddenInsets;
+2 −2
Original line number Diff line number Diff line
@@ -54,8 +54,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro

        @Override
        @UiThread
        public void startAnimation(InsetsAnimationControlImpl controller,
                WindowInsetsAnimationControlListener listener, int types,
        public <T extends InsetsAnimationControlRunner & WindowInsetsAnimationController>
        void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
                WindowInsetsAnimation animation, Bounds bounds) {
            // Animation will be started in constructor already.
        }
+64 −25
Original line number Diff line number Diff line
@@ -202,6 +202,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
    private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;

    /** Visible for WindowManagerWrapper */
    public static final int ANIMATION_DURATION_RESIZE = 300;

    private static final int ANIMATION_DELAY_DIM_MS = 500;

    private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
@@ -232,6 +235,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
            new PathInterpolator(0.4f, 0f, 1f, 1f);

    /** Visible for WindowManagerWrapper */
    public static final Interpolator RESIZE_INTERPOLATOR = new LinearInterpolator();

    /** The amount IME will move up/down when animating in floating mode. */
    private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;

@@ -285,9 +291,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @VisibleForTesting
    public static final int ANIMATION_TYPE_USER = 2;

    /** Running animation will resize insets */
    @VisibleForTesting
    public static final int ANIMATION_TYPE_RESIZE = 3;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
            ANIMATION_TYPE_USER})
            ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
    @interface AnimationType {
    }

@@ -317,7 +327,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        private final boolean mDisable;
        private final int mFloatingImeBottomInset;

        private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
        private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
                new ThreadLocal<AnimationHandler>() {
            @Override
            protected AnimationHandler initialValue() {
@@ -549,7 +559,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
    private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
    private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
    private final ArraySet<InsetsSourceConsumer> mRequestedVisibilityChanged = new ArraySet<>();
    private WindowInsets mLastInsets;

@@ -569,7 +578,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    private int mCaptionInsetsHeight = 0;
    private boolean mAnimationsDisabled;

    private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
    private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
    private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
            = new ArrayList<>();

@@ -579,7 +588,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    /** Set of inset types which cannot be controlled by the user animation */
    private @InsetsType int mDisabledUserAnimationInsetsTypes;

    private Runnable mInvokeControllableInsetsChangedListeners =
    private final Runnable mInvokeControllableInsetsChangedListeners =
            this::invokeControllableInsetsChangedListeners;

    public InsetsController(Host host) {
@@ -607,23 +616,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            }

            final List<WindowInsetsAnimation> runningAnimations = new ArrayList<>();
            final List<WindowInsetsAnimation> finishedAnimations = new ArrayList<>();
            final InsetsState state = new InsetsState(mState, true /* copySources */);
            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
                RunningAnimation runningAnimation = mRunningAnimations.get(i);
                if (DEBUG) Log.d(TAG, "Running animation type: " + runningAnimation.type);
                InsetsAnimationControlRunner runner = runningAnimation.runner;
                if (runner instanceof InsetsAnimationControlImpl) {
                    InsetsAnimationControlImpl control = (InsetsAnimationControlImpl) runner;
                final InsetsAnimationControlRunner runner = runningAnimation.runner;
                if (runner instanceof WindowInsetsAnimationController) {

                    // Keep track of running animation to be dispatched. Aggregate it here such that
                    // if it gets finished within applyChangeInsets we still dispatch it to
                    // onProgress.
                    if (runningAnimation.startDispatched) {
                        runningAnimations.add(control.getAnimation());
                        runningAnimations.add(runner.getAnimation());
                    }

                    if (control.applyChangeInsets(state)) {
                        mTmpFinishedControls.add(control);
                    if (((WindowInsetsAnimationController) runner).applyChangeInsets(state)) {
                        finishedAnimations.add(runner.getAnimation());
                    }
                }
            }
@@ -641,10 +650,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                }
            }

            for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
                dispatchAnimationEnd(mTmpFinishedControls.get(i).getAnimation());
            for (int i = finishedAnimations.size() - 1; i >= 0; i--) {
                dispatchAnimationEnd(finishedAnimations.get(i));
            }
            mTmpFinishedControls.clear();
        };
    }

@@ -690,15 +698,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                true /* excludeInvisibleIme */)) {
            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
            mHost.notifyInsetsChanged();
            startResizingAnimationIfNeeded(lastState);
        }
        return true;
    }

    private void updateState(InsetsState newState) {
        mState.setDisplayFrame(newState.getDisplayFrame());
        mState.setDisplayCutout(newState.getDisplayCutout());
        mState.setRoundedCorners(newState.getRoundedCorners());
        mState.setPrivacyIndicatorBounds(newState.getPrivacyIndicatorBounds());
        mState.set(newState, 0 /* types */);
        @InsetsType int disabledUserAnimationTypes = 0;
        @InsetsType int[] cancelledUserAnimationTypes = {0};
        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
@@ -763,6 +769,39 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        return false;
    }

    private void startResizingAnimationIfNeeded(InsetsState fromState) {
        if (!fromState.getDisplayFrame().equals(mState.getDisplayFrame())) {
            return;
        }
        @InsetsType int types = 0;
        InsetsState toState = null;
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(Type.systemBars());
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            final @InternalInsetsType int type = internalTypes.valueAt(i);
            final InsetsSource fromSource = fromState.peekSource(type);
            final InsetsSource toSource = mState.peekSource(type);
            if (fromSource != null && toSource != null
                    && fromSource.isVisible() && toSource.isVisible()
                    && !fromSource.getFrame().equals(toSource.getFrame())
                    && (Rect.intersects(mFrame, fromSource.getFrame())
                            || Rect.intersects(mFrame, toSource.getFrame()))) {
                types |= InsetsState.toPublicType(toSource.getType());
                if (toState == null) {
                    toState = new InsetsState();
                }
                toState.addSource(new InsetsSource(toSource));
            }
        }
        if (types == 0) {
            return;
        }
        cancelExistingControllers(types);
        final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
                mFrame, fromState, toState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, types,
                this);
        mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
    }

    /**
     * @see InsetsState#calculateInsets
     */
@@ -784,7 +823,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    /**
     * @see InsetsState#calculateVisibleInsets(Rect, int)
     */
    public Rect calculateVisibleInsets(@SoftInputModeFlags int softInputMode) {
    public Insets calculateVisibleInsets(@SoftInputModeFlags int softInputMode) {
        return mState.calculateVisibleInsets(mFrame, softInputMode);
    }

@@ -1473,12 +1512,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    @VisibleForTesting
    @Override
    public void startAnimation(InsetsAnimationControlImpl controller,
            WindowInsetsAnimationControlListener listener, int types,
    public <T extends InsetsAnimationControlRunner & WindowInsetsAnimationController>
    void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
            WindowInsetsAnimation animation, Bounds bounds) {
        mHost.dispatchWindowInsetsAnimationPrepare(animation);
        mHost.addOnPreDrawRunnable(() -> {
            if (controller.isCancelled()) {
            if (runner.isCancelled()) {
                if (WARN) Log.w(TAG, "startAnimation canceled before preDraw");
                return;
            }
@@ -1486,15 +1525,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    "InsetsAnimation: " + WindowInsets.Type.toString(types), types);
            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
                RunningAnimation runningAnimation = mRunningAnimations.get(i);
                if (runningAnimation.runner == controller) {
                if (runningAnimation.runner == runner) {
                    runningAnimation.startDispatched = true;
                }
            }
            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
            mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
            mStartingAnimation = true;
            controller.mReadyDispatched = true;
            listener.onReady(controller, types);
            runner.setReadyDispatched(true);
            listener.onReady(runner, types);
            mStartingAnimation = false;
        });
    }
+235 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.view;

import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA;
import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED;
import static android.view.InsetsAnimationControlImplProto.IS_FINISHED;
import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA;
import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION;
import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Insets;
import android.graphics.Rect;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;

/**
 * Runs a fake animation of resizing insets to produce insets animation callbacks.
 * @hide
 */
public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner,
        WindowInsetsAnimationController, WindowInsetsAnimationControlListener {

    private final InsetsState mFromState;
    private final InsetsState mToState;
    private final @InsetsType int mTypes;
    private final WindowInsetsAnimation mAnimation;
    private final InsetsAnimationControlCallbacks mController;
    private ValueAnimator mAnimator;
    private boolean mCancelled;
    private boolean mFinished;

    public InsetsResizeAnimationRunner(Rect frame, InsetsState fromState, InsetsState toState,
            Interpolator interpolator, long duration, @InsetsType int types,
            InsetsAnimationControlCallbacks controller) {
        mFromState = fromState;
        mToState = toState;
        mTypes = types;
        mController = controller;
        mAnimation = new WindowInsetsAnimation(types, interpolator, duration);
        mAnimation.setAlpha(1f);
        final Insets fromInsets = fromState.calculateInsets(
                frame, types, false /* ignoreVisibility */);
        final Insets toInsets = toState.calculateInsets(
                frame, types, false /* ignoreVisibility */);
        controller.startAnimation(this, this, types, mAnimation,
                new Bounds(Insets.min(fromInsets, toInsets), Insets.max(fromInsets, toInsets)));
    }

    @Override
    public int getTypes() {
        return mTypes;
    }

    @Override
    public int getControllingTypes() {
        return mTypes;
    }

    @Override
    public WindowInsetsAnimation getAnimation() {
        return mAnimation;
    }

    @Override
    public int getAnimationType() {
        return ANIMATION_TYPE_RESIZE;
    }

    @Override
    public void cancel() {
        if (mCancelled || mFinished) {
            return;
        }
        mCancelled = true;
        if (mAnimator != null) {
            mAnimator.cancel();
        }
    }

    @Override
    public boolean isCancelled() {
        return mCancelled;
    }

    @Override
    public void onReady(WindowInsetsAnimationController controller, int types) {
        if (mCancelled) {
            return;
        }
        mAnimator = ValueAnimator.ofFloat(0f, 1f);
        mAnimator.setDuration(mAnimation.getDurationMillis());
        mAnimator.addUpdateListener(animation -> {
            mAnimation.setFraction(animation.getAnimatedFraction());
            mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this);
        });
        mAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                mFinished = true;
                mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this);
            }
        });
        mAnimator.start();
    }

    @Override
    public boolean applyChangeInsets(InsetsState outState) {
        final float fraction = mAnimation.getInterpolatedFraction();
        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
            final InsetsSource fromSource = mFromState.peekSource(type);
            final InsetsSource toSource = mToState.peekSource(type);
            if (fromSource == null || toSource == null) {
                continue;
            }
            final Rect fromFrame = fromSource.getFrame();
            final Rect toFrame = toSource.getFrame();
            final Rect frame = new Rect(
                    (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
                    (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
                    (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
                    (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
            final InsetsSource source = new InsetsSource(type);
            source.setFrame(frame);
            source.setVisible(toSource.isVisible());
            outState.addSource(source);
        }
        if (mFinished) {
            mController.notifyFinished(this, true /* shown */);
        }
        return mFinished;
    }

    @Override
    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(IS_CANCELLED, mCancelled);
        proto.write(IS_FINISHED, mFinished);
        proto.write(TMP_MATRIX, "null");
        proto.write(PENDING_INSETS, "null");
        proto.write(PENDING_FRACTION, mAnimation.getInterpolatedFraction());
        proto.write(SHOWN_ON_FINISH, true);
        proto.write(CURRENT_ALPHA, 1f);
        proto.write(PENDING_ALPHA, 1f);
        proto.end(token);
    }

    @Override
    public Insets getHiddenStateInsets() {
        return Insets.NONE;
    }

    @Override
    public Insets getShownStateInsets() {
        return Insets.NONE;
    }

    @Override
    public Insets getCurrentInsets() {
        return Insets.NONE;
    }

    @Override
    public float getCurrentFraction() {
        return 0;
    }

    @Override
    public float getCurrentAlpha() {
        return 0;
    }

    @Override
    public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
    }

    @Override
    public void finish(boolean shown) {
    }

    @Override
    public boolean isFinished() {
        return false;
    }

    @Override
    public void notifyControlRevoked(int types) {
    }

    @Override
    public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
    }

    @Override
    public boolean hasZeroInsetsIme() {
        return false;
    }

    @Override
    public void setReadyDispatched(boolean dispatched) {
    }

    @Override
    public void onFinished(WindowInsetsAnimationController controller) {
    }

    @Override
    public void onCancelled(WindowInsetsAnimationController controller) {
    }
}
Loading