summaryrefslogtreecommitdiff
path: root/core/java/android/inputmethodservice/InputMethodService.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/inputmethodservice/InputMethodService.java')
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java185
1 files changed, 185 insertions, 0 deletions
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 43842c5c3403..da70359332a1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -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()) {
@@ -493,6 +512,18 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+ handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -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 */);
@@ -686,6 +814,63 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * 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
* all of the standard behavior for an input method session.