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

Commit bc67f2e1 authored by Adam He's avatar Adam He
Browse files

API for autofill integration with keyboard.

Bug: 137800469
Test: manual verification
Change-Id: Id222500c373898d576661cacb7a1cb51061041d4
parent 2447db9d
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -22611,6 +22611,7 @@ package android.inputmethodservice {
    method public void onConfigureWindow(android.view.Window, boolean, boolean);
    method public android.view.View onCreateCandidatesView();
    method public android.view.View onCreateExtractTextView();
    method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest();
    method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
    method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
    method public android.view.View onCreateInputView();
@@ -22627,6 +22628,7 @@ package android.inputmethodservice {
    method public void onFinishInput();
    method public void onFinishInputView(boolean);
    method public void onInitializeInterface();
    method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
    method public boolean onKeyDown(int, android.view.KeyEvent);
    method public boolean onKeyLongPress(int, android.view.KeyEvent);
    method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -41446,7 +41448,8 @@ package android.service.autofill {
    method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
    method public int getFlags();
    method public int getId();
    method public void writeToParcel(android.os.Parcel, int);
    method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
    field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
    field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
@@ -41463,6 +41466,7 @@ package android.service.autofill {
  public static final class FillResponse.Builder {
    ctor public FillResponse.Builder();
    method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
    method @NonNull public android.service.autofill.FillResponse.Builder addInlineSuggestionSlice(@NonNull android.app.slice.Slice);
    method @NonNull public android.service.autofill.FillResponse build();
    method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
    method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+18 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.inputmethodservice;
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -28,6 +29,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.InputChannel;
import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -39,6 +41,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
@@ -72,6 +75,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
    private static final int DO_SHOW_SOFT_INPUT = 60;
    private static final int DO_HIDE_SOFT_INPUT = 70;
    private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
    private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;

    final WeakReference<AbstractInputMethodService> mTarget;
    final Context mContext;
@@ -225,6 +229,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
            case DO_CHANGE_INPUTMETHOD_SUBTYPE:
                inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
                return;
            case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
                SomeArgs args = (SomeArgs) msg.obj;
                inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
                        (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
                return;
        }
        Log.w(TAG, "Unhandled message code: " + msg.what);
    }
@@ -265,6 +274,15 @@ class IInputMethodWrapper extends IInputMethod.Stub
                mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps));
    }

    @BinderThread
    @Override
    public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId,
            IInlineSuggestionsRequestCallback cb) {
        mCaller.executeOrSendMessage(
                mCaller.obtainMessageOOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, componentName,
                        autofillId, cb));
    }

    @BinderThread
    @Override
    public void bindInput(InputBinding binding) {
+185 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.AnyThread;
@@ -34,6 +36,7 @@ import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +49,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
@@ -68,11 +73,14 @@ import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.autofill.AutofillId;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
@@ -89,11 +97,14 @@ import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Collections;

/**
@@ -434,6 +445,14 @@ public class InputMethodService extends AbstractInputMethodService {
    final Insets mTmpInsets = new Insets();
    final int[] mTmpLocation = new int[2];

    @Nullable
    private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;

    @Nullable
    private InlineSuggestionsResponseCallbackImpl mInlineSuggestionsResponseCallback = null;

    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);

    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
        onComputeInsets(mTmpInsets);
        if (isExtractViewShown()) {
@@ -491,6 +510,18 @@ public class InputMethodService extends AbstractInputMethodService {
            attachToken(token);
        }

        /**
         * {@inheritDoc}
         * @hide
         */
        @MainThread
        @Override
        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
                AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
            Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
            handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
        }

        /**
         * {@inheritDoc}
         */
@@ -668,6 +699,103 @@ public class InputMethodService extends AbstractInputMethodService {
        }
    }

    // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
    /**
     * Returns an {@link InlineSuggestionsRequest} to be sent to Autofill.
     *
     * <p>Should be implemented by subclasses.</p>
     */
    public @Nullable InlineSuggestionsRequest onCreateInlineSuggestionsRequest() {
        return null;
    }

    /**
     * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing
     * inline suggestions.
     *
     * <p>Should be implemented by subclasses.</p>
     *
     * @param response {@link InlineSuggestionsResponse} passed back by Autofill.
     * @return Whether the IME will use and render  the inline suggestions.
     */
    public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
        return false;
    }

    /**
     * Returns whether inline suggestions are enabled on this service.
     *
     * TODO(b/137800469): check XML for value.
     */
    private boolean isInlineSuggestionsEnabled() {
        return true;
    }

    /**
     * Sends an {@link InlineSuggestionsRequest} obtained from
     * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
     */
    private void makeInlineSuggestionsRequest() {
        if (mInlineSuggestionsRequestInfo == null) {
            Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
            return;
        }

        final IInlineSuggestionsRequestCallback requestCallback =
                mInlineSuggestionsRequestInfo.mCallback;
        try {
            final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
            if (request == null) {
                Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
                requestCallback.onInlineSuggestionsUnsupported();
            } else {
                if (mInlineSuggestionsResponseCallback == null) {
                    mInlineSuggestionsResponseCallback =
                            new InlineSuggestionsResponseCallbackImpl(this,
                                    mInlineSuggestionsRequestInfo.mComponentName,
                                    mInlineSuggestionsRequestInfo.mFocusedId);
                }
                requestCallback.onInlineSuggestionsRequest(request,
                        mInlineSuggestionsResponseCallback);
            }
        } catch (RemoteException e) {
            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
        }
    }

    private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
            @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
        mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
                callback);

        if (!isInlineSuggestionsEnabled()) {
            try {
                callback.onInlineSuggestionsUnsupported();
            } catch (RemoteException e) {
                Log.w(TAG, "handleMakeInlineSuggestionsRequest() RemoteException:" + e);
            }
            return;
        }

        if (!mInputStarted) {
            Log.w(TAG, "onStartInput() not called yet");
            return;
        }

        makeInlineSuggestionsRequest();
    }

    private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
            @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
        if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
            Log.d(TAG, "Response component=" + componentName + " differs from request component="
                    + mInlineSuggestionsRequestInfo.mComponentName + ", ignoring response");
            return;
        }
        onInlineSuggestionsResponse(response);
    }

    private void notifyImeHidden() {
        setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
        onPreRenderedWindowVisibilityChanged(false /* setVisible */);
@@ -685,6 +813,63 @@ public class InputMethodService extends AbstractInputMethodService {
        inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r));
    }

    /**
     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
     */
    private static final class InlineSuggestionsResponseCallbackImpl
            extends IInlineSuggestionsResponseCallback.Stub {
        private final WeakReference<InputMethodService> mInputMethodService;

        private final ComponentName mRequestComponentName;
        private final AutofillId mRequestAutofillId;

        private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
                ComponentName componentName, AutofillId autofillId) {
            mInputMethodService = new WeakReference<>(inputMethodService);
            mRequestComponentName = componentName;
            mRequestAutofillId = autofillId;
        }

        @Override
        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
                throws RemoteException {
            final InputMethodService service = mInputMethodService.get();
            if (service != null) {
                service.mHandler.sendMessage(obtainMessage(
                        InputMethodService::handleOnInlineSuggestionsResponse, service,
                        mRequestComponentName, mRequestAutofillId, response));
            }
        }
    }

    /**
     * Information about incoming requests from Autofill Frameworks for inline suggestions.
     */
    private static final class InlineSuggestionsRequestInfo {
        final ComponentName mComponentName;
        final AutofillId mFocusedId;
        final IInlineSuggestionsRequestCallback mCallback;

        InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
                IInlineSuggestionsRequestCallback callback) {
            this.mComponentName = componentName;
            this.mFocusedId = focusedId;
            this.mCallback = callback;
        }

        /**
         * Returns whether the cached {@link ComponentName} matches the passed in activity.
         */
        public boolean validate(ComponentName componentName) {
            final boolean result = componentName.equals(mComponentName);
            if (!result) {
                Log.d(TAG, "Cached request info ComponentName=" + mComponentName
                        + " differs from received ComponentName=" + componentName);
            }
            return result;
        }
    }

    /**
     * Concrete implementation of
     * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
+83 −24
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import android.view.inputmethod.InlineSuggestionsRequest;

import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
@@ -116,20 +117,34 @@ public final class FillRequest implements Parcelable {
     */
    private final @RequestFlags int mFlags;

    /**
     * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
     * with this request.
     *
     * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
     *
     * @return the suggestionspec
     */
    private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;

    private void onConstructed() {
        Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
    }



    // Code below generated by codegen v1.0.0.
    // Code below generated by codegen v1.0.14.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
    //
    // To regenerate run:
    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
    //
    // CHECKSTYLE:OFF Generated code
    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
    //   Settings > Editor > Code Style > Formatter Control
    //@formatter:off


    /** @hide */
    @IntDef(flag = true, prefix = "FLAG_", value = {
@@ -184,6 +199,11 @@ public final class FillRequest implements Parcelable {
     *
     *   @return any combination of {@link #FLAG_MANUAL_REQUEST} and
     *           {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
     * @param inlineSuggestionsRequest
     *   Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
     *   with this request.
     *
     *   TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
     * @hide
     */
    @DataClass.Generated.Member
@@ -191,7 +211,8 @@ public final class FillRequest implements Parcelable {
            int id,
            @NonNull List<FillContext> fillContexts,
            @Nullable Bundle clientState,
            @RequestFlags int flags) {
            @RequestFlags int flags,
            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
        this.mId = id;
        this.mFillContexts = fillContexts;
        com.android.internal.util.AnnotationValidations.validate(
@@ -203,6 +224,7 @@ public final class FillRequest implements Parcelable {
                mFlags,
                FLAG_MANUAL_REQUEST
                        | FLAG_COMPATIBILITY_MODE_REQUEST);
        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;

        onConstructed();
    }
@@ -256,6 +278,19 @@ public final class FillRequest implements Parcelable {
        return mFlags;
    }

    /**
     * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
     * with this request.
     *
     * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
     *
     * @return the suggestionspec
     */
    @DataClass.Generated.Member
    public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
        return mInlineSuggestionsRequest;
    }

    @Override
    @DataClass.Generated.Member
    public String toString() {
@@ -266,40 +301,36 @@ public final class FillRequest implements Parcelable {
                "id = " + mId + ", " +
                "fillContexts = " + mFillContexts + ", " +
                "clientState = " + mClientState + ", " +
                "flags = " + requestFlagsToString(mFlags) +
                "flags = " + requestFlagsToString(mFlags) + ", " +
                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
        " }";
    }

    @Override
    @DataClass.Generated.Member
    public void writeToParcel(Parcel dest, int flags) {
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        // You can override field parcelling by defining methods like:
        // void parcelFieldName(Parcel dest, int flags) { ... }

        byte flg = 0;
        if (mClientState != null) flg |= 0x4;
        if (mInlineSuggestionsRequest != null) flg |= 0x10;
        dest.writeByte(flg);
        dest.writeInt(mId);
        dest.writeParcelableList(mFillContexts, flags);
        if (mClientState != null) dest.writeBundle(mClientState);
        dest.writeInt(mFlags);
        if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
    }

    @Override
    @DataClass.Generated.Member
    public int describeContents() { return 0; }

    @DataClass.Generated.Member
    public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
            = new Parcelable.Creator<FillRequest>() {
        @Override
        public FillRequest[] newArray(int size) {
            return new FillRequest[size];
        }

        @Override
    /** @hide */
    @SuppressWarnings({"unchecked", "RedundantCast"})
        public FillRequest createFromParcel(Parcel in) {
    @DataClass.Generated.Member
    /* package-private */ FillRequest(@NonNull Parcel in) {
        // You can override field unparcelling by defining methods like:
        // static FieldType unparcelFieldName(Parcel in) { ... }

@@ -309,20 +340,48 @@ public final class FillRequest implements Parcelable {
        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
        Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
        int flags = in.readInt();
            return new FillRequest(
                    id,
                    fillContexts,
                    clientState,
                    flags);
        InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);

        this.mId = id;
        this.mFillContexts = fillContexts;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mFillContexts);
        this.mClientState = clientState;
        this.mFlags = flags;

        Preconditions.checkFlagsArgument(
                mFlags,
                FLAG_MANUAL_REQUEST
                        | FLAG_COMPATIBILITY_MODE_REQUEST);
        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;

        onConstructed();
    }

    @DataClass.Generated.Member
    public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
            = new Parcelable.Creator<FillRequest>() {
        @Override
        public FillRequest[] newArray(int size) {
            return new FillRequest[size];
        }

        @Override
        public FillRequest createFromParcel(@NonNull Parcel in) {
            return new FillRequest(in);
        }
    };

    @DataClass.Generated(
            time = 1565152134349L,
            codegenVersion = "1.0.0",
            time = 1575928271155L,
            codegenVersion = "1.0.14",
            sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
    @Deprecated
    private void __metadata() {}


    //@formatter:on
    // End of generated code

}
+38 −2

File changed.

Preview size limit exceeded, changes collapsed.

Loading