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

Commit 5e6f9545 authored by Tiger's avatar Tiger
Browse files

Let InsetsController control requested visible types

We plan to make the insets source consumer exist only when there is a
corresponding insets source, which means the consumer won't always
exist. This CL makes InsetsController set the requested types on its
own, instead of telling the consumer to do so. Thus, show and hide are
removed from InsetsSourceConsumer. This CL adds onAnimationStateChanged
to the consumer, so that it can apply the requested visibility at the
proper timing.

Bug: 234093736
Test: atest ImeInsetsSourceConsumerTest InsetsAnimationControlImplTest
      InsetsControllerTest InsetsSourceConsumerTest
      WindowInsetsControllerTests CtsInputMethodTestCases
Change-Id: Ib54c75e5fb1188c83074e06abf7af509974af2b1
parent e1434019
Loading
Loading
Loading
Loading
+51 −38
Original line number Diff line number Diff line
@@ -16,21 +16,20 @@

package android.view;

import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsState.ITYPE_IME;

import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.inputmethod.ImeTracing;

import java.util.function.Supplier;

/**
@@ -61,6 +60,38 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
        super(ITYPE_IME, state, transactionSupplier, controller);
    }

    @Override
    public boolean onAnimationStateChanged(boolean running) {
        if (!running) {
            ImeTracing.getInstance().triggerClientDump(
                    "ImeInsetsSourceConsumer#onAnimationFinished",
                    mController.getHost().getInputMethodManager(), null /* icProto */);
        }
        final boolean insetsChanged = super.onAnimationStateChanged(running);
        final boolean showRequested = (mController.getRequestedVisibleTypes() & getType()) != 0;
        if (showRequested) {
            onShowRequested();
        } else {
            mIsRequestedVisibleAwaitingControl = false;
            if (!running) {
                // Remove IME surface as IME has finished hide animation, if there is no pending
                // show request.
                if (!mIsShowRequestedDuringHideAnimation) {
                    notifyHidden();
                    removeSurface();
                }
            }
            // Here is reached
            // (1) before the hide animation starts.
            // (2) after the hide animation ends.
            // (3) if the IME is not controllable (animationFinished == true in this case).
            // We should reset mIsShowRequestedDuringHideAnimation in all cases.
            mIsHideAnimationRunning = running;
            mIsShowRequestedDuringHideAnimation = false;
        }
        return insetsChanged;
    }

    @Override
    public void onWindowFocusGained(boolean hasViewFocus) {
        super.onWindowFocusGained(hasViewFocus);
@@ -78,36 +109,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
    }

    @Override
    public void show(boolean fromIme) {
        super.show(fromIme);
        onShowRequested();
    }

    @Override
    public void hide() {
        super.hide();
        mIsRequestedVisibleAwaitingControl = false;
    }

    @Override
    void hide(boolean animationFinished, @AnimationType int animationType) {
        hide();

        if (animationFinished) {
            // Remove IME surface as IME has finished hide animation, if there is no pending
            // show request.
            if (!mIsShowRequestedDuringHideAnimation) {
                notifyHidden();
                removeSurface();
            }
        }
        // This method is called
        // (1) before the hide animation starts.
        // (2) after the hide animation ends.
        // (3) if the IME is not controllable (animationFinished == true in this case).
        // We should reset mIsShowRequestedDuringHideAnimation in all cases.
        mIsHideAnimationRunning = !animationFinished;
        mIsShowRequestedDuringHideAnimation = false;
    public boolean applyLocalVisibilityOverride() {
        ImeTracing.getInstance().triggerClientDump(
                "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
                mController.getHost().getInputMethodManager(), null /* icProto */);
        return super.applyLocalVisibilityOverride();
    }

    /**
@@ -116,6 +122,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
     */
    @Override
    public @ShowResult int requestShow(boolean fromIme) {
        if (fromIme) {
            ImeTracing.getInstance().triggerClientDump(
                    "ImeInsetsSourceConsumer#requestShow",
                    mController.getHost().getInputMethodManager(), null /* icProto */);
        }

        // TODO: ResultReceiver for IME.
        // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
        if (getControl() == null) {
@@ -137,10 +149,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
     * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
     * IME insets are hidden.
     */
    @Override
    void notifyHidden() {
    private void notifyHidden() {
        getImm().notifyImeHidden(mController.getHost().getWindowToken());
        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
    }

    @Override
@@ -154,11 +164,13 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
    @Override
    public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes,
            int[] hideTypes) {
        ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl",
                mController.getHost().getInputMethodManager(), null /* icProto */);
        if (!super.setControl(control, showTypes, hideTypes)) {
            return false;
        }
        if (control == null && !mIsRequestedVisibleAwaitingControl) {
            hide();
            mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
            removeSurface();
        }
        if (control != null) {
@@ -192,7 +204,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
    }

    /**
     * Called when {@link #show} or {@link InputMethodManager#showSoftInput(View, int)} is called.
     * Called when {@link #onAnimationStateChanged(boolean)} or
     * {@link InputMethodManager#showSoftInput(View, int)} is called.
     */
    public void onShowRequested() {
        if (mIsHideAnimationRunning) {
+79 −95
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.view.InsetsControllerProto.CONTROL;
import static android.view.InsetsControllerProto.STATE;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.toInternalType;
import static android.view.InsetsState.toPublicType;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.FIRST;
@@ -742,6 +741,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    private void updateState(InsetsState newState) {
        mState.set(newState, 0 /* types */);
        @InsetsType int existingTypes = 0;
        @InsetsType int visibleTypes = 0;
        @InsetsType int disabledUserAnimationTypes = 0;
        @InsetsType int[] cancelledUserAnimationTypes = {0};
@@ -760,10 +760,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                }
            }
            getSourceConsumer(type).updateSource(source, animationType);
            existingTypes |= insetsType;
            if (source.isVisible()) {
                visibleTypes |= insetsType;
            }
        }

        // If a type doesn't have a source, treat it as visible if it is visible by default.
        visibleTypes |= WindowInsets.Type.defaultVisible() & ~existingTypes;

        if (mVisibleTypes != visibleTypes) {
            if (WindowInsets.Type.hasCompatSystemBars(mVisibleTypes ^ visibleTypes)) {
                mCompatSysUiVisibilityStaled = true;
@@ -1103,6 +1108,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            @AnimationType int animationType,
            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
        final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;

        // Basically, we accept the requested visibilities from the upstream callers...
        setRequestedVisibleTypes(visible ? types : 0, types);

        // However, we might reject the request in some cases, such as delaying showing IME or
        // rejecting showing IME.
        controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
                durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
                useInsetsAnimationThread, statsToken);

        // We are finishing setting the requested visible types. Report them to the server and/or
        // the app.
        reportRequestedVisibleTypes();
    }

    private void controlAnimationUncheckedInner(@InsetsType int types,
            @Nullable CancellationSignal cancellationSignal,
            WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
            long durationMs, Interpolator interpolator,
            @AnimationType int animationType,
            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
        if ((types & mTypesBeingCancelled) != 0) {
            throw new IllegalStateException("Cannot start a new insets animation of "
@@ -1119,13 +1147,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    && !mState.getSource(ITYPE_IME).isVisible()) {
                // We've requested IMM to show IME, but the IME is not controllable. We need to
                // cancel the request.
                getSourceConsumer(ITYPE_IME).hide(true, animationType);
                setRequestedVisibleTypes(0 /* visibleTypes */, ime());
                if (getSourceConsumer(ITYPE_IME).onAnimationStateChanged(false /* running */)) {
                    notifyVisibilityChanged();
                }
            }
        }
        if (types == 0) {
            // nothing to animate.
            listener.onCancelled(null);
            reportRequestedVisibleTypes();
            if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
            return;
        }
@@ -1161,7 +1191,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    }
                });
            }
            reportRequestedVisibleTypes();

            // The requested visibilities should be delayed as well. Otherwise, the server will
            // create already visible leashes for us before we play the show animation.
            setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);

            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
            return;
        }
@@ -1169,7 +1203,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        if (typesReady == 0) {
            if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
            listener.onCancelled(null);
            reportRequestedVisibleTypes();
            return;
        }

@@ -1198,12 +1231,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        } else {
            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
        }
        if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
            showDirectly(types, fromIme);
        } else {
            hideDirectly(types, false /* animationFinished */, animationType, fromIme);
        onAnimationStateChanged(types, true /* running */);

        if (fromIme) {
            switch (animationType) {
                case ANIMATION_TYPE_SHOW:
                    Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
                    break;
                case ANIMATION_TYPE_HIDE:
                    Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
                    break;
            }
        } else if (animationType == ANIMATION_TYPE_HIDE) {
            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
        }
        reportRequestedVisibleTypes();
    }

    // TODO(b/242962223): Make this setter restrictive.
@@ -1228,11 +1269,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            boolean canRun = true;
            if (show) {
                // Show request
                if (fromIme) {
                    ImeTracing.getInstance().triggerClientDump(
                            "ImeInsetsSourceConsumer#requestShow", mHost.getInputMethodManager(),
                            null /* icProto */);
                }
                switch(consumer.requestShow(fromIme)) {
                    case ShowResult.SHOW_IMMEDIATELY:
                        break;
@@ -1246,15 +1282,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                        // IME cannot be shown (since it didn't have focus), proceed
                        // with animation of other types.
                        canRun = false;

                        // Reject the show request.
                        setRequestedVisibleTypes(0 /* visibleTypes */, consumer.getType());
                        break;
                }
            } else {
                // Hide request
                // TODO: Move notifyHidden() to beginning of the hide animation
                // (when visibility actually changes using hideDirectly()).
                if (!fromIme) {
                    consumer.notifyHidden();
                }
            }
            if (!canRun) {
                if (WARN) Log.w(TAG, String.format(
@@ -1266,24 +1298,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            if (control != null && control.getLeash() != null) {
                controls.put(control.getId(), new InsetsSourceControl(control));
                typesReady |= consumer.getType();
            } else if (animationType == ANIMATION_TYPE_SHOW) {
                if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
                        + fromIme);
                // We don't have a control at the moment. However, we still want to update requested
                // visibility state such that in case we get control, we can apply show animation.
                if (fromIme) {
                    ImeTracing.getInstance().triggerClientDump(
                            "InsetsSourceConsumer#show", mHost.getInputMethodManager(),
                            null /* icProto */);
                }
                consumer.show(fromIme);
            } else if (animationType == ANIMATION_TYPE_HIDE) {
                if (fromIme) {
                    ImeTracing.getInstance().triggerClientDump(
                            "InsetsSourceConsumer#hide", mHost.getInputMethodManager(),
                            null /* icProto */);
                }
                consumer.hide();
            }
        }
        return new Pair<>(typesReady, imeReady);
@@ -1332,6 +1346,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @VisibleForTesting
    @Override
    public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
        setRequestedVisibleTypes(shown ? runner.getTypes() : 0, runner.getTypes());
        cancelAnimation(runner, false /* invokeCallback */);
        if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown);
        if (runner.getAnimationType() == ANIMATION_TYPE_RESIZE) {
@@ -1343,15 +1358,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        if (shown) {
            ImeTracker.get().onProgress(statsToken,
                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
            showDirectly(runner.getTypes(), true /* fromIme */);
            ImeTracker.get().onShown(statsToken);
        } else {
            ImeTracker.get().onProgress(statsToken,
                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
            hideDirectly(runner.getTypes(), true /* animationFinished */,
                    runner.getAnimationType(), true /* fromIme */);
            ImeTracker.get().onHidden(statsToken);
        }
        reportRequestedVisibleTypes();
    }

    @Override
@@ -1388,28 +1401,31 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    "cancelAnimation of types: %d, animType: %d, host: %s",
                    control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
        }
        boolean stateChanged = false;
        @InsetsType int removedTypes = 0;
        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
            RunningAnimation runningAnimation = mRunningAnimations.get(i);
            if (runningAnimation.runner == control) {
                mRunningAnimations.remove(i);
                ArraySet<Integer> types = toInternalType(control.getTypes());
                for (int j = types.size() - 1; j >= 0; j--) {
                    if (types.valueAt(j) == ITYPE_IME) {
                        ImeTracing.getInstance().triggerClientDump(
                                "InsetsSourceConsumer#notifyAnimationFinished",
                                mHost.getInputMethodManager(), null /* icProto */);
                    }
                    stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished();
                }
                removedTypes = control.getTypes();
                if (invokeCallback) {
                    dispatchAnimationEnd(runningAnimation.runner.getAnimation());
                }
                break;
            }
        }
        if (stateChanged) {
            mHost.notifyInsetsChanged();
        onAnimationStateChanged(removedTypes, false /* running */);
    }

    private void onAnimationStateChanged(@InsetsType int types, boolean running) {
        boolean insetsChanged = false;
        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
            if ((consumer.getType() & types) != 0) {
                insetsChanged |= consumer.onAnimationStateChanged(running);
            }
        }
        if (insetsChanged) {
            notifyVisibilityChanged();
        }
    }

@@ -1472,27 +1488,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        return ANIMATION_TYPE_NONE;
    }

    void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
        final @InsetsType int requestedVisibleTypes =
                (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
        if (mRequestedVisibleTypes != requestedVisibleTypes) {
            if (WindowInsets.Type.hasCompatSystemBars(
                    mRequestedVisibleTypes ^ requestedVisibleTypes)) {
                mCompatSysUiVisibilityStaled = true;
            }
            mRequestedVisibleTypes = requestedVisibleTypes;
        }
    }

    /**
     * Sends the requested visible types to window manager if any of them is changed.
     * Called when finishing setting requested visible types or finishing setting controls.
     */
    private void reportRequestedVisibleTypes() {
        updateCompatSysUiVisibility();
        if (mReportedRequestedVisibleTypes != mRequestedVisibleTypes) {
            final @InsetsType int diff = mRequestedVisibleTypes ^ mReportedRequestedVisibleTypes;
            if (WindowInsets.Type.hasCompatSystemBars(diff)) {
                mCompatSysUiVisibilityStaled = true;
            }
            mReportedRequestedVisibleTypes = mRequestedVisibleTypes;
            mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes);
        }
        updateCompatSysUiVisibility();
    }

    @VisibleForTesting
@@ -1538,39 +1555,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
    }

    private void hideDirectly(@InsetsType int types, boolean animationFinished,
            @AnimationType int animationType, boolean fromIme) {
        if ((types & ime()) != 0) {
            ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
                    mHost.getInputMethodManager(), null /* icProto */);
        }
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
        }
        reportRequestedVisibleTypes();

        if (fromIme) {
            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
        }
    }

    private void showDirectly(@InsetsType int types, boolean fromIme) {
        if ((types & ime()) != 0) {
            ImeTracing.getInstance().triggerClientDump("InsetsController#showDirectly",
                    mHost.getInputMethodManager(), null /* icProto */);
        }
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
        }
        reportRequestedVisibleTypes();

        if (fromIme) {
            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
        }
    }

    /**
     * Cancel on-going animation to show/hide {@link InsetsType}.
     */
+22 −60

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -102,7 +102,8 @@ public class InsetsAnimationControlImplTest {
                new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
                        mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
                new int[1], new int[1]);
        navConsumer.hide();
        mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars());
        navConsumer.applyLocalVisibilityOverride();

        SparseArray<InsetsSourceControl> controls = new SparseArray<>();
        controls.put(ITYPE_STATUS_BAR, topConsumer.getControl());
+6 −13
Original line number Diff line number Diff line
@@ -132,17 +132,10 @@ public class InsetsControllerTest {

                        private boolean mImeRequestedShow;

                        @Override
                        public void show(boolean fromIme) {
                            super.show(fromIme);
                            if (fromIme) {
                                mImeRequestedShow = true;
                            }
                        }

                        @Override
                        public int requestShow(boolean fromController) {
                            if (fromController || mImeRequestedShow) {
                                mImeRequestedShow = true;
                                return SHOW_IMMEDIATELY;
                            } else {
                                return IME_SHOW_DELAYED;
@@ -815,10 +808,10 @@ public class InsetsControllerTest {
        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
            prepareControls();

            // Hiding visible system bars should only causes insets change once for each bar.
            // Calling to hide system bars once should only cause insets change once.
            clearInvocations(mTestHost);
            mController.hide(statusBars() | navigationBars());
            verify(mTestHost, times(2)).notifyInsetsChanged();
            verify(mTestHost, times(1)).notifyInsetsChanged();

            // Sending the same insets state should not cause insets change.
            // This simulates the callback from server after hiding system bars.
@@ -826,10 +819,10 @@ public class InsetsControllerTest {
            mController.onStateChanged(mController.getState());
            verify(mTestHost, never()).notifyInsetsChanged();

            // Showing invisible system bars should only causes insets change once for each bar.
            // Calling to show system bars once should only cause insets change once.
            clearInvocations(mTestHost);
            mController.show(statusBars() | navigationBars());
            verify(mTestHost, times(2)).notifyInsetsChanged();
            verify(mTestHost, times(1)).notifyInsetsChanged();

            // Sending the same insets state should not cause insets change.
            // This simulates the callback from server after showing system bars.
@@ -912,7 +905,7 @@ public class InsetsControllerTest {
            assertNull(imeInsetsConsumer.getControl());

            // Verify IME requested visibility should be updated to IME consumer from controller.
            mController.show(ime());
            mController.show(ime(), true /* fromIme */, null /* statsToken */);
            assertTrue(isRequestedVisible(mController, ime()));

            mController.hide(ime());
Loading