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

Commit 46d59f0e authored by Tarandeep Singh's avatar Tarandeep Singh
Browse files

Link InsetsController to IME (IME transitons 4/n)

With this change, InsetsController.show/hide now links to IME. This also
takes care of animating IME along with other types.
Insets API are reactive i.e. they remain in sync with state of IME.

Test: atest InsetsControllerTest
Test: atest ImeInsetsConsumerTest
Bug: 118118435

Change-Id: Ib3997487bd19351d1d23bc70173fc9bdfd23a704
parent 215929b6
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
    private static final int DO_TOGGLE_SOFT_INPUT = 105;
    private static final int DO_FINISH_SESSION = 110;
    private static final int DO_VIEW_CLICKED = 115;
    private static final int DO_NOTIFY_IME_HIDDEN = 120;

    HandlerCaller mCaller;
    InputMethodSession mInputMethodSession;
@@ -129,6 +130,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
                mInputMethodSession.viewClicked(msg.arg1 == 1);
                return;
            }
            case DO_NOTIFY_IME_HIDDEN: {
                mInputMethodSession.notifyImeHidden();
                return;
            }
        }
        Log.w(TAG, "Unhandled message code: " + msg.what);
    }
@@ -171,6 +176,11 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
                mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
    }

    @Override
    public void notifyImeHidden() {
        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN));
    }

    @Override
    public void updateCursor(Rect newCursor) {
        mCaller.executeOrSendMessage(
+13 −1
Original line number Diff line number Diff line
@@ -592,7 +592,6 @@ public class InputMethodService extends AbstractInputMethodService {
            final boolean wasVisible = mIsPreRendered
                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
            if (mIsPreRendered) {
                // TODO: notify visibility to insets consumer.
                if (DEBUG) {
                    Log.v(TAG, "Making IME window invisible");
                }
@@ -658,6 +657,11 @@ public class InputMethodService extends AbstractInputMethodService {
        }
    }

    private void notifyImeHidden() {
        setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
        onPreRenderedWindowVisibilityChanged(false /* setVisible */);
    }

    private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
        mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
    }
@@ -760,6 +764,14 @@ public class InputMethodService extends AbstractInputMethodService {
            }
            InputMethodService.this.onUpdateCursorAnchorInfo(info);
        }

        /**
         * Notify IME that window is hidden.
         * @hide
         */
        public final void notifyImeHidden() {
            InputMethodService.this.notifyImeHidden();
        }
    }
    
    /**
+6 −0
Original line number Diff line number Diff line
@@ -290,6 +290,12 @@ final class MultiClientInputMethodClientCallbackAdaptor {
                        CallbackImpl::updateCursorAnchorInfo, mCallbackImpl, info));
            }
        }

        @Override
        public final void notifyImeHidden() {
            // no-op for multi-session since IME is responsible controlling navigation bar buttons.
            reportNotSupported();
        }
    }

    private static final class MultiClientInputMethodSessionImpl
+26 −6
Original line number Diff line number Diff line
@@ -18,10 +18,10 @@ package android.view;

import static android.view.InsetsState.TYPE_IME;

import android.inputmethodservice.InputMethodService;
import android.os.Parcel;
import android.text.TextUtils;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;

@@ -73,11 +73,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
            return;
        }

        if (setVisible) {
            mController.show(Type.IME);
        } else {
            mController.hide(Type.IME);
        }
        mController.applyImeVisibility(setVisible);
    }

    @Override
@@ -91,6 +87,30 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
        mHasWindowFocus = false;
    }

    /**
     * Request {@link InputMethodManager} to show the IME.
     * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
     */
    @Override
    @ShowResult int requestShow(boolean fromIme) {
        // TODO: ResultReceiver for IME.
        // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
        if (fromIme) {
            return ShowResult.SHOW_IMMEDIATELY;
        }

        return getImm().requestImeShow(null /* resultReceiver */)
                ? ShowResult.SHOW_DELAYED : ShowResult.SHOW_FAILED;
    }

    /**
     * Notify {@link InputMethodService} that IME window is hidden.
     */
    @Override
    void notifyHidden() {
        getImm().notifyImeHidden();
    }

    private boolean isDummyOrEmptyEditor(EditorInfo info) {
        // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
        return info == null || (info.fieldId <= 0 && info.inputType <= 0);
+118 −11
Original line number Diff line number Diff line
@@ -29,10 +29,13 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Property;
import android.util.SparseArray;
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetType;

import com.android.internal.annotations.VisibleForTesting;
@@ -51,6 +54,7 @@ public class InsetsController implements WindowInsetsController {
    private static final int DIRECTION_NONE = 0;
    private static final int DIRECTION_SHOW = 1;
    private static final int DIRECTION_HIDE = 2;

    @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
    private @interface AnimationDirection{}

@@ -103,6 +107,8 @@ public class InsetsController implements WindowInsetsController {
    private ObjectAnimator mAnimator;
    private @AnimationDirection int mAnimationDirection;

    private int mPendingTypesToShow;

    public InsetsController(ViewRootImpl viewRoot) {
        mViewRoot = viewRoot;
        mAnimCallback = () -> {
@@ -193,6 +199,12 @@ public class InsetsController implements WindowInsetsController {

    @Override
    public void show(@InsetType int types) {
        show(types, false /* fromIme */);
    }

    private void show(@InsetType int types, boolean fromIme) {
        // TODO: Support a ResultReceiver for IME.
        // TODO(b/123718661): Make show() work for multi-session IME.
        int typesReady = 0;
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -201,15 +213,18 @@ public class InsetsController implements WindowInsetsController {
                // Only one animator (with multiple InsetType) can run at a time.
                // previous one should be cancelled for simplicity.
                cancelExistingAnimation();
            } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
                // no-op: already shown or animating in.
            } else if (consumer.isVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
                // no-op: already shown or animating in (because window visibility is
                // applied before starting animation).
                // TODO: When we have more than one types: handle specific case when
                // show animation is going on, but the current type is not becoming visible.
                continue;
            }
            typesReady |= InsetsState.toPublicType(consumer.getType());
        }
        applyAnimation(typesReady, true /* show */);
        applyAnimation(typesReady, true /* show */, fromIme);
    }

    @Override
@@ -220,35 +235,114 @@ public class InsetsController implements WindowInsetsController {
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            if (mAnimationDirection == DIRECTION_SHOW) {
                cancelExistingAnimation();
            } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
            } else if (!consumer.isVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
                // no-op: already hidden or animating out.
                continue;
            }
            typesReady |= InsetsState.toPublicType(consumer.getType());
        }
        applyAnimation(typesReady, false /* show */);
        applyAnimation(typesReady, false /* show */, false /* fromIme */);
    }

    @Override
    public void controlWindowInsetsAnimation(@InsetType int types,
            WindowInsetsAnimationControlListener listener) {
        controlWindowInsetsAnimation(types, listener, false /* fromIme */);
    }

    private void controlWindowInsetsAnimation(@InsetType int types,
            WindowInsetsAnimationControlListener listener, boolean fromIme) {
        if (types == 0) {
            // nothing to animate.
            return;
        }

        // TODO: Check whether we already have a controller.
        final ArraySet<Integer> internalTypes = mState.toInternalType(types);
        final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();

        Pair<Integer, Boolean> typesReadyPair = collectConsumers(fromIme, internalTypes, consumers);
        int typesReady = typesReadyPair.first;
        boolean isReady = typesReadyPair.second;
        if (!isReady) {
            // IME isn't ready, all requested types would be shown once IME is ready.
            mPendingTypesToShow = typesReady;
            // TODO: listener for pending types.
            return;
        }

        // pending types from previous request.
        typesReady = collectPendingConsumers(typesReady, consumers);

        if (typesReady == 0) {
            listener.onCancelled();
            return;
        }

        final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
                mFrame, mState, listener, typesReady,
                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
        mAnimationControls.add(controller);
    }

    /**
     * @return Pair of (types ready to animate, is ready to animate).
     */
    private Pair<Integer, Boolean> collectConsumers(boolean fromIme,
            ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers) {
        int typesReady = 0;
        boolean isReady = true;
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            if (consumer.getControl() != null) {
                if (!consumer.isVisible()) {
                    // Show request
                    switch(consumer.requestShow(fromIme)) {
                        case ShowResult.SHOW_IMMEDIATELY:
                            typesReady |= InsetsState.toPublicType(TYPE_IME);
                            break;
                        case ShowResult.SHOW_DELAYED:
                            isReady = false;
                            break;
                        case ShowResult.SHOW_FAILED:
                            // IME cannot be shown (since it didn't have focus), proceed
                            // with animation of other types.
                            if (mPendingTypesToShow != 0) {
                                // remove IME from pending because view no longer has focus.
                                mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
                            }
                            break;
                    }
                } else {
                    // Hide request
                    // TODO: Move notifyHidden() to beginning of the hide animation
                    // (when visibility actually changes using hideDirectly()).
                    consumer.notifyHidden();
                    typesReady |= InsetsState.toPublicType(consumer.getType());
                }
                consumers.put(consumer.getType(), consumer);
            } else {
                // TODO: Let calling app know it's not possible, or wait
                // TODO: Remove it from types
            }
        }
        final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
                mFrame, mState, listener, types,
                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
        mAnimationControls.add(controller);
        return new Pair<>(typesReady, isReady);
    }

    private int collectPendingConsumers(@InsetType int typesReady,
            SparseArray<InsetsSourceConsumer> consumers) {
        if (mPendingTypesToShow != 0) {
            typesReady |= mPendingTypesToShow;
            final ArraySet<Integer> internalTypes = mState.toInternalType(mPendingTypesToShow);
            for (int i = internalTypes.size() - 1; i >= 0; i--) {
                InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
                consumers.put(consumer.getType(), consumer);
            }
            mPendingTypesToShow = 0;
        }
        return typesReady;
    }

    private void applyLocalVisibilityOverride() {
@@ -293,6 +387,19 @@ public class InsetsController implements WindowInsetsController {
        return mViewRoot;
    }

    /**
     * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
     * @hide
     */
    @VisibleForTesting
    public void applyImeVisibility(boolean setVisible) {
        if (setVisible) {
            show(Type.IME, true /* fromIme */);
        } else {
            hide(Type.IME);
        }
    }

    private InsetsSourceConsumer createConsumerOfType(int type) {
        if (type == TYPE_IME) {
            return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
@@ -321,7 +428,7 @@ public class InsetsController implements WindowInsetsController {
        }
    }

    private void applyAnimation(@InsetType final int types, boolean show) {
    private void applyAnimation(@InsetType final int types, boolean show, boolean fromIme) {
        if (types == 0) {
            // nothing to animate.
            return;
@@ -366,7 +473,7 @@ public class InsetsController implements WindowInsetsController {
        // TODO: Instead of clearing this here, properly wire up
        // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
        mAnimationControls.clear();
        controlWindowInsetsAnimation(types, listener);
        controlWindowInsetsAnimation(types, listener, fromIme);
    }

    private void hideDirectly(@InsetType int types) {
Loading