summaryrefslogtreecommitdiff
path: root/core/java/android/inputmethodservice
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-09-01 20:04:08 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-09-01 20:04:08 +0000
commitff2ffe48acb657c959cae265529a7290013c7db8 (patch)
tree6d05301b67632d384a392190306ecb088c896cb2 /core/java/android/inputmethodservice
parentb57f32e68ab2f57fd4ade254d524a4efce9ad30f (diff)
parent628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (diff)
Merge "Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)" into stage-aosp-master
Diffstat (limited to 'core/java/android/inputmethodservice')
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java6
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java10
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java79
-rw-r--r--core/java/android/inputmethodservice/InlineSuggestionSession.java250
-rw-r--r--core/java/android/inputmethodservice/InlineSuggestionSessionController.java262
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java284
-rw-r--r--core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java41
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java21
8 files changed, 879 insertions, 74 deletions
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index b0fca006d1e2..d7ca63a54987 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -260,4 +260,10 @@ public abstract class AbstractInputMethodService extends Service
*/
public void notifyUserActionIfNecessary() {
}
+
+ /** @hide */
+ @Override
+ public final boolean isUiContext() {
+ return true;
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9327b241c6c5..e9de27456f97 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -53,6 +53,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
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;
+ private static final int DO_REMOVE_IME_SURFACE = 130;
@UnsupportedAppUsage
HandlerCaller mCaller;
@@ -136,6 +137,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.notifyImeHidden();
return;
}
+ case DO_REMOVE_IME_SURFACE: {
+ mInputMethodSession.removeImeSurface();
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -184,6 +189,11 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
@Override
+ public void removeImeSurface() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
+ }
+
+ @Override
public void updateCursor(Rect newCursor) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 69b93b19fc41..a298c856a0fb 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@ package android.inputmethodservice;
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -37,12 +38,15 @@ import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.CancellationGroup;
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;
import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
import com.android.internal.view.InputConnectionWrapper;
import java.io.FileDescriptor;
@@ -50,7 +54,6 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -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;
@@ -87,12 +91,13 @@ class IInputMethodWrapper extends IInputMethod.Stub
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
- * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
* {@link #unbindInput()} are called with the same order as the original calls
* in {@link com.android.server.inputmethod.InputMethodManagerService}.
* See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
*/
- AtomicBoolean mIsUnbindIssued = null;
+ @Nullable
+ CancellationGroup mCancellationGroup = null;
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
@@ -184,11 +189,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
- final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+ final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
SomeArgs moreArgs = (SomeArgs) args.arg5;
final InputConnection ic = inputContext != null
? new InputConnectionWrapper(
- mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+ mTarget, inputContext, moreArgs.argi3, cancellationGroup)
: null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(
@@ -216,15 +221,31 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_REVOKE_SESSION:
inputMethod.revokeSession((InputMethodSession)msg.obj);
return;
- case DO_SHOW_SOFT_INPUT:
- inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+ case DO_SHOW_SOFT_INPUT: {
+ final SomeArgs args = (SomeArgs)msg.obj;
+ inputMethod.showSoftInputWithToken(
+ msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ args.recycle();
return;
- case DO_HIDE_SOFT_INPUT:
- inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+ }
+ case DO_HIDE_SOFT_INPUT: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
+ (IBinder) args.arg1);
+ args.recycle();
return;
+ }
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
+ case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+ final SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.onCreateInlineSuggestionsRequest(
+ (InlineSuggestionsRequestInfo) args.arg1,
+ (IInlineSuggestionsRequestCallback) args.arg2);
+ args.recycle();
+ return;
+
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -267,16 +288,24 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
+ public void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
+ IInlineSuggestionsRequestCallback cb) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, requestInfo, cb));
+ }
+
+ @BinderThread
+ @Override
public void bindInput(InputBinding binding) {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
}
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
- mIsUnbindIssued);
+ mCancellationGroup);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
@@ -284,10 +313,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
public void unbindInput() {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
// Signal the flag then forget it.
- mIsUnbindIssued.set(true);
- mIsUnbindIssued = null;
+ mCancellationGroup.cancelAll();
+ mCancellationGroup = null;
} else {
Log.e(TAG, "unbindInput must be paired with bindInput.");
}
@@ -299,16 +328,16 @@ class IInputMethodWrapper extends IInputMethod.Stub
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
- if (mIsUnbindIssued == null) {
+ if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
}
SomeArgs args = SomeArgs.obtain();
args.argi1 = restarting ? 1 : 0;
args.argi2 = shouldPreRenderIme ? 1 : 0;
args.argi3 = missingMethods;
- mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
- DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
+ inputContext, attribute, mCancellationGroup, args));
}
@BinderThread
@@ -353,16 +382,16 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
- public void showSoftInput(int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
- flags, resultReceiver));
+ public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
+ flags, showInputToken, resultReceiver));
}
@BinderThread
@Override
- public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
- flags, resultReceiver));
+ public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
+ flags, hideInputToken, resultReceiver));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java
new file mode 100644
index 000000000000..90d0ff0a5026
--- /dev/null
+++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020 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.inputmethodservice;
+
+import static android.inputmethodservice.InputMethodService.DEBUG;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Maintains an inline suggestion session with the autofill manager service.
+ *
+ * <p> Each session correspond to one request from the Autofill manager service to create an
+ * {@link InlineSuggestionsRequest}. It's responsible for calling back to the Autofill manager
+ * service with {@link InlineSuggestionsRequest} and receiving {@link InlineSuggestionsResponse}
+ * from it.
+ * <p>
+ * TODO(b/151123764): currently the session may receive responses for different views on the same
+ * screen, but we will fix it so each session corresponds to one view.
+ *
+ * <p> All the methods are expected to be called from the main thread, to ensure thread safety.
+ */
+class InlineSuggestionSession {
+ private static final String TAG = "ImsInlineSuggestionSession";
+
+ static final InlineSuggestionsResponse EMPTY_RESPONSE = new InlineSuggestionsResponse(
+ Collections.emptyList());
+
+ @NonNull
+ private final Handler mMainThreadHandler;
+ @NonNull
+ private final InlineSuggestionSessionController mInlineSuggestionSessionController;
+ @NonNull
+ private final InlineSuggestionsRequestInfo mRequestInfo;
+ @NonNull
+ private final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull
+ private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier;
+ @NonNull
+ private final Supplier<IBinder> mHostInputTokenSupplier;
+ @NonNull
+ private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
+ // Indicate whether the previous call to the mResponseConsumer is empty or not. If it hasn't
+ // been called yet, the value would be null.
+ @Nullable
+ private Boolean mPreviousResponseIsEmpty;
+
+
+ /**
+ * Indicates whether {@link #makeInlineSuggestionRequestUncheck()} has been called or not,
+ * because it should only be called at most once.
+ */
+ @Nullable
+ private boolean mCallbackInvoked = false;
+ @Nullable
+ private InlineSuggestionsResponseCallbackImpl mResponseCallback;
+
+ InlineSuggestionSession(@NonNull InlineSuggestionsRequestInfo requestInfo,
+ @NonNull IInlineSuggestionsRequestCallback callback,
+ @NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier,
+ @NonNull Supplier<IBinder> hostInputTokenSupplier,
+ @NonNull Consumer<InlineSuggestionsResponse> responseConsumer,
+ @NonNull InlineSuggestionSessionController inlineSuggestionSessionController,
+ @NonNull Handler mainThreadHandler) {
+ mRequestInfo = requestInfo;
+ mCallback = callback;
+ mRequestSupplier = requestSupplier;
+ mHostInputTokenSupplier = hostInputTokenSupplier;
+ mResponseConsumer = responseConsumer;
+ mInlineSuggestionSessionController = inlineSuggestionSessionController;
+ mMainThreadHandler = mainThreadHandler;
+ }
+
+ @MainThread
+ InlineSuggestionsRequestInfo getRequestInfo() {
+ return mRequestInfo;
+ }
+
+ @MainThread
+ IInlineSuggestionsRequestCallback getRequestCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns true if the session should send Ime status updates to Autofill.
+ *
+ * <p> The session only starts to send Ime status updates to Autofill after the sending back
+ * an {@link InlineSuggestionsRequest}.
+ */
+ @MainThread
+ boolean shouldSendImeStatus() {
+ return mResponseCallback != null;
+ }
+
+ /**
+ * Returns true if {@link #makeInlineSuggestionRequestUncheck()} is called. It doesn't not
+ * necessarily mean an {@link InlineSuggestionsRequest} was sent, because it may call {@link
+ * IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}.
+ *
+ * <p> The callback should be invoked at most once for each session.
+ */
+ @MainThread
+ boolean isCallbackInvoked() {
+ return mCallbackInvoked;
+ }
+
+ /**
+ * Invalidates the current session so it doesn't process any further {@link
+ * InlineSuggestionsResponse} from Autofill.
+ *
+ * <p> This method should be called when the session is de-referenced from the {@link
+ * InlineSuggestionSessionController}.
+ */
+ @MainThread
+ void invalidate() {
+ try {
+ mCallback.onInlineSuggestionsSessionInvalidated();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInlineSuggestionsSessionInvalidated() remote exception:" + e);
+ }
+ if (mResponseCallback != null) {
+ consumeInlineSuggestionsResponse(EMPTY_RESPONSE);
+ mResponseCallback.invalidate();
+ mResponseCallback = null;
+ }
+ }
+
+ /**
+ * Gets the {@link InlineSuggestionsRequest} from IME and send it back to the Autofill if it's
+ * not null.
+ *
+ * <p>Calling this method implies that the input is started on the view corresponding to the
+ * session.
+ */
+ @MainThread
+ void makeInlineSuggestionRequestUncheck() {
+ if (mCallbackInvoked) {
+ return;
+ }
+ try {
+ final InlineSuggestionsRequest request = mRequestSupplier.apply(
+ mRequestInfo.getUiExtras());
+ if (request == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+ }
+ mCallback.onInlineSuggestionsUnsupported();
+ } else {
+ request.setHostInputToken(mHostInputTokenSupplier.get());
+ request.filterContentTypes();
+ mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
+ mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+ }
+ mCallbackInvoked = true;
+ }
+
+ @MainThread
+ void handleOnInlineSuggestionsResponse(@NonNull AutofillId fieldId,
+ @NonNull InlineSuggestionsResponse response) {
+ if (!mInlineSuggestionSessionController.match(fieldId)) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "IME receives response: " + response.getInlineSuggestions().size());
+ }
+ consumeInlineSuggestionsResponse(response);
+ }
+
+ @MainThread
+ void consumeInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+ boolean isResponseEmpty = response.getInlineSuggestions().isEmpty();
+ if (isResponseEmpty && Boolean.TRUE.equals(mPreviousResponseIsEmpty)) {
+ // No-op if both the previous response and current response are empty.
+ return;
+ }
+ mPreviousResponseIsEmpty = isResponseEmpty;
+ mResponseConsumer.accept(response);
+ }
+
+ /**
+ * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+ */
+ private static final class InlineSuggestionsResponseCallbackImpl extends
+ IInlineSuggestionsResponseCallback.Stub {
+ private final WeakReference<InlineSuggestionSession> mSession;
+ private volatile boolean mInvalid = false;
+
+ private InlineSuggestionsResponseCallbackImpl(InlineSuggestionSession session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ void invalidate() {
+ mInvalid = true;
+ }
+
+ @BinderThread
+ @Override
+ public void onInlineSuggestionsResponse(AutofillId fieldId,
+ InlineSuggestionsResponse response) {
+ if (mInvalid) {
+ return;
+ }
+ final InlineSuggestionSession session = mSession.get();
+ if (session != null) {
+ session.mMainThreadHandler.sendMessage(
+ obtainMessage(InlineSuggestionSession::handleOnInlineSuggestionsResponse,
+ session, fieldId, response));
+ }
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSessionController.java b/core/java/android/inputmethodservice/InlineSuggestionSessionController.java
new file mode 100644
index 000000000000..071c096ee2f0
--- /dev/null
+++ b/core/java/android/inputmethodservice/InlineSuggestionSessionController.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2020 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.inputmethodservice;
+
+import static android.inputmethodservice.InputMethodService.DEBUG;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Manages the interaction with the autofill manager service for the inline suggestion sessions.
+ *
+ * <p>
+ * The class maintains the inline suggestion session with the autofill service. There is at most one
+ * active inline suggestion session at any given time.
+ *
+ * <p>
+ * The class receives the IME status change events (input start/finish, input view start/finish, and
+ * show input requested result), and send them through IPC to the {@link
+ * com.android.server.inputmethod.InputMethodManagerService}, which sends them to {@link
+ * com.android.server.autofill.InlineSuggestionSession} in the Autofill manager service. If there is
+ * no open inline suggestion session, no event will be send to autofill manager service.
+ *
+ * <p>
+ * All the methods are expected to be called from the main thread, to ensure thread safety.
+ */
+class InlineSuggestionSessionController {
+ private static final String TAG = "InlineSuggestionSessionController";
+
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), null, true);
+
+ @NonNull
+ private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier;
+ @NonNull
+ private final Supplier<IBinder> mHostInputTokenSupplier;
+ @NonNull
+ private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
+
+ /* The following variables track the IME status */
+ @Nullable
+ private String mImeClientPackageName;
+ @Nullable
+ private AutofillId mImeClientFieldId;
+ private boolean mImeInputStarted;
+ private boolean mImeInputViewStarted;
+
+ @Nullable
+ private InlineSuggestionSession mSession;
+
+ InlineSuggestionSessionController(
+ @NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier,
+ @NonNull Supplier<IBinder> hostInputTokenSupplier,
+ @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
+ mRequestSupplier = requestSupplier;
+ mHostInputTokenSupplier = hostInputTokenSupplier;
+ mResponseConsumer = responseConsumer;
+ }
+
+ /**
+ * Called upon IME receiving a create inline suggestion request. Must be called in the main
+ * thread to ensure thread safety.
+ */
+ @MainThread
+ void onMakeInlineSuggestionsRequest(@NonNull InlineSuggestionsRequestInfo requestInfo,
+ @NonNull IInlineSuggestionsRequestCallback callback) {
+ if (DEBUG) Log.d(TAG, "onMakeInlineSuggestionsRequest: " + requestInfo);
+ // Creates a new session for the new create request from Autofill.
+ if (mSession != null) {
+ mSession.invalidate();
+ }
+ mSession = new InlineSuggestionSession(requestInfo, callback, mRequestSupplier,
+ mHostInputTokenSupplier, mResponseConsumer, this, mMainThreadHandler);
+
+ // If the input is started on the same view, then initiate the callback to the Autofill.
+ // Otherwise wait for the input to start.
+ if (mImeInputStarted && match(mSession.getRequestInfo())) {
+ mSession.makeInlineSuggestionRequestUncheck();
+ // ... then update the Autofill whether the input view is started.
+ if (mImeInputViewStarted) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling {@link InputMethodService#onStartInput(EditorInfo,
+ * boolean)}. This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnStartInput(@Nullable String imeClientPackageName,
+ @Nullable AutofillId imeFieldId) {
+ if (DEBUG) Log.d(TAG, "notifyOnStartInput: " + imeClientPackageName + ", " + imeFieldId);
+ if (imeClientPackageName == null || imeFieldId == null) {
+ return;
+ }
+ mImeInputStarted = true;
+ mImeClientPackageName = imeClientPackageName;
+ mImeClientFieldId = imeFieldId;
+
+ if (mSession != null) {
+ mSession.consumeInlineSuggestionsResponse(InlineSuggestionSession.EMPTY_RESPONSE);
+ // Initiates the callback to Autofill if there is a pending matching session.
+ // Otherwise updates the session with the Ime status.
+ if (!mSession.isCallbackInvoked() && match(mSession.getRequestInfo())) {
+ mSession.makeInlineSuggestionRequestUncheck();
+ } else if (mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInput(mImeClientFieldId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInput() remote exception:" + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread after getting results from
+ * {@link InputMethodService#dispatchOnShowInputRequested(int,
+ * boolean)}. This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnShowInputRequested(boolean requestResult) {
+ if (DEBUG) Log.d(TAG, "notifyShowInputRequested");
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodShowInputRequested(requestResult);
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodShowInputRequested() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling
+ * {@link InputMethodService#onStartInputView(EditorInfo,
+ * boolean)} . This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnStartInputView() {
+ if (DEBUG) Log.d(TAG, "notifyOnStartInputView");
+ mImeInputViewStarted = true;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodStartInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling
+ * {@link InputMethodService#onFinishInputView(boolean)}.
+ * This method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnFinishInputView() {
+ if (DEBUG) Log.d(TAG, "notifyOnFinishInputView");
+ mImeInputViewStarted = false;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodFinishInputView();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodFinishInputView() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Called from IME main thread before calling {@link InputMethodService#onFinishInput()}. This
+ * method should be quick as it makes a unblocking IPC.
+ */
+ @MainThread
+ void notifyOnFinishInput() {
+ if (DEBUG) Log.d(TAG, "notifyOnFinishInput");
+ mImeClientPackageName = null;
+ mImeClientFieldId = null;
+ mImeInputViewStarted = false;
+ mImeInputStarted = false;
+ if (mSession != null && mSession.shouldSendImeStatus()) {
+ try {
+ mSession.getRequestCallback().onInputMethodFinishInput();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onInputMethodFinishInput() remote exception:" + e);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the current Ime focused field matches the session {@code requestInfo}.
+ */
+ @MainThread
+ boolean match(@Nullable InlineSuggestionsRequestInfo requestInfo) {
+ return match(requestInfo, mImeClientPackageName, mImeClientFieldId);
+ }
+
+ /**
+ * Returns true if the current Ime focused field matches the {@code autofillId}.
+ */
+ @MainThread
+ boolean match(@Nullable AutofillId autofillId) {
+ return match(autofillId, mImeClientFieldId);
+ }
+
+ private static boolean match(
+ @Nullable InlineSuggestionsRequestInfo inlineSuggestionsRequestInfo,
+ @Nullable String imeClientPackageName, @Nullable AutofillId imeClientFieldId) {
+ if (inlineSuggestionsRequestInfo == null || imeClientPackageName == null
+ || imeClientFieldId == null) {
+ return false;
+ }
+ return inlineSuggestionsRequestInfo.getComponentName().getPackageName().equals(
+ imeClientPackageName) && match(inlineSuggestionsRequestInfo.getAutofillId(),
+ imeClientFieldId);
+ }
+
+ private static boolean match(@Nullable AutofillId autofillId,
+ @Nullable AutofillId imeClientFieldId) {
+ // The IME doesn't have information about the virtual view id for the child views in the
+ // web view, so we are only comparing the parent view id here. This means that for cases
+ // where there are two input fields in the web view, they will have the same view id
+ // (although different virtual child id), and we will not be able to distinguish them.
+ return autofillId != null && imeClientFieldId != null
+ && autofillId.getViewId() == imeClientFieldId.getViewId();
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 44825a8f1502..c5a11abe1136 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,9 +16,11 @@
package android.inputmethodservice;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -34,6 +36,7 @@ import android.app.ActivityManager;
import android.app.Dialog;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -41,6 +44,7 @@ import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.Region;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -62,8 +66,11 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Side;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.CompletionInfo;
@@ -71,6 +78,8 @@ 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;
@@ -81,17 +90,21 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.window.WindowMetricsHelper;
import com.android.internal.annotations.GuardedBy;
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.InlineSuggestionsRequestInfo;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
/**
* InputMethodService provides a standard implementation of an InputMethod,
@@ -431,7 +444,37 @@ public class InputMethodService extends AbstractInputMethodService {
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ private InlineSuggestionSessionController mInlineSuggestionSessionController;
+
+ private boolean mAutomotiveHideNavBarForKeyboard;
+ private boolean mIsAutomotive;
+
+ /**
+ * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
+ * The original app window token is passed from client app window.
+ * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy
+ * token to identify this window.
+ * This dummy token is only valid for a single call to {@link InputMethodImpl#showSoftInput},
+ * after which it is set null until next call.
+ */
+ private IBinder mCurShowInputToken;
+
+ /**
+ * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#hideSoftInput}
+ * The original app window token is passed from client app window.
+ * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy
+ * token to identify this window.
+ * This dummy token is only valid for a single call to {@link InputMethodImpl#hideSoftInput},
+ * after which it is set {@code null} until next call.
+ */
+ private IBinder mCurHideInputToken;
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+ onComputeInsets(mTmpInsets);
+ if (!mViewsCreated) {
+ // The IME views are not ready, keep visible insets untouched.
+ mTmpInsets.visibleTopInsets = 0;
+ }
if (isExtractViewShown()) {
// In true fullscreen mode, we just say the window isn't covering
// any content so we don't impact whatever is behind.
@@ -440,12 +483,15 @@ public class InputMethodService extends AbstractInputMethodService {
info.touchableRegion.setEmpty();
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
} else {
- onComputeInsets(mTmpInsets);
info.contentInsets.top = mTmpInsets.contentTopInsets;
info.visibleInsets.top = mTmpInsets.visibleTopInsets;
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
+
+ if (mInputFrame != null) {
+ setImeExclusionRect(mTmpInsets.visibleTopInsets);
+ }
};
final View.OnClickListener mActionClickListener = v -> {
@@ -466,6 +512,10 @@ public class InputMethodService extends AbstractInputMethodService {
* all of the standard behavior for an input method.
*/
public class InputMethodImpl extends AbstractInputMethodImpl {
+
+ private boolean mSystemCallingShowSoftInput;
+ private boolean mSystemCallingHideSoftInput;
+
/**
* {@inheritDoc}
* @hide
@@ -486,6 +536,21 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void onCreateInlineSuggestionsRequest(
+ @NonNull InlineSuggestionsRequestInfo requestInfo,
+ @NonNull IInlineSuggestionsRequestCallback cb) {
+ if (DEBUG) {
+ Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+ }
+ mInlineSuggestionSessionController.onMakeInlineSuggestionsRequest(requestInfo, cb);
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -506,12 +571,10 @@ public class InputMethodService extends AbstractInputMethodService {
@Override
public void updateInputMethodDisplay(int displayId) {
// Update display for adding IME window to the right display.
- if (displayId != DEFAULT_DISPLAY) {
- // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
- // for update resources & configuration correctly when show soft input
- // in non-default display.
- updateDisplay(displayId);
- }
+ // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
+ // for update resources & configuration correctly when show soft input
+ // in non-default display.
+ updateDisplay(displayId);
}
/**
@@ -541,6 +604,7 @@ public class InputMethodService extends AbstractInputMethodService {
public void unbindInput() {
if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+ " ic=" + mInputConnection);
+ // Unbind input is per process per display.
onUnbindInput();
mInputBinding = null;
mInputConnection = null;
@@ -588,19 +652,40 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder hideInputToken) {
+ mSystemCallingHideSoftInput = true;
+ mCurHideInputToken = hideInputToken;
+ hideSoftInput(flags, resultReceiver);
+ mCurHideInputToken = null;
+ mSystemCallingHideSoftInput = false;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
+ && !mSystemCallingHideSoftInput) {
+ Log.e(TAG, "IME shouldn't call hideSoftInput on itself."
+ + " Use requestHideSelf(int) itself");
+ return;
+ }
final boolean wasVisible = mIsPreRendered
? mDecorViewVisible && mWindowVisible : isInputViewShown();
+ applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */);
if (mIsPreRendered) {
if (DEBUG) {
Log.v(TAG, "Making IME window invisible");
}
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
- applyVisibilityInInsetsConsumer(false /* setVisible */);
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
} else {
mShowInputFlags = 0;
@@ -620,11 +705,33 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder showInputToken) {
+ mSystemCallingShowSoftInput = true;
+ mCurShowInputToken = showInputToken;
+ showSoftInput(flags, resultReceiver);
+ mCurShowInputToken = null;
+ mSystemCallingShowSoftInput = false;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
+ // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
+ if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
+ && !mSystemCallingShowSoftInput) {
+ Log.e(TAG," IME shouldn't call showSoftInput on itself."
+ + " Use requestShowSelf(int) itself");
+ return;
+ }
final boolean wasVisible = mIsPreRendered
? mDecorViewVisible && mWindowVisible : isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
@@ -632,11 +739,11 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) {
Log.v(TAG, "Making IME window visible");
}
- applyVisibilityInInsetsConsumer(true /* setVisible */);
onPreRenderedWindowVisibilityChanged(true /* setVisible */);
} else {
showWindow(true);
}
+ applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */);
}
// If user uses hard keyboard, IME button should always be shown.
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -659,17 +766,96 @@ public class InputMethodService extends AbstractInputMethodService {
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
dispatchOnCurrentInputMethodSubtypeChanged(subtype);
}
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public void setCurrentShowInputToken(IBinder showInputToken) {
+ mCurShowInputToken = showInputToken;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public void setCurrentHideInputToken(IBinder hideInputToken) {
+ mCurHideInputToken = hideInputToken;
+ }
+ }
+
+ /**
+ * Called when Autofill is requesting an {@link InlineSuggestionsRequest} from the IME.
+ *
+ * <p>The Autofill Framework will first request the IME to create and send an
+ * {@link InlineSuggestionsRequest} back. Once Autofill Framework receives a valid request and
+ * also receives valid inline suggestions, they will be returned via
+ * {@link #onInlineSuggestionsResponse(InlineSuggestionsResponse)}.</p>
+ *
+ * <p>IME Lifecycle - The request will wait to be created after inputStarted</p>
+ *
+ * <p>If the IME wants to support displaying inline suggestions, they must set
+ * supportsInlineSuggestions in its XML and implement this method to return a valid
+ * {@link InlineSuggestionsRequest}.</p>
+ *
+ * @param uiExtras the extras that contain the UI renderer related information
+ * @return an {@link InlineSuggestionsRequest} to be sent to Autofill.
+ */
+ @Nullable
+ public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(@NonNull Bundle uiExtras) {
+ 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 the {@link IBinder} input token from the host view root.
+ */
+ @Nullable
+ private IBinder getHostInputToken() {
+ ViewRootImpl viewRoot = null;
+ if (mRootView != null) {
+ viewRoot = mRootView.getViewRootImpl();
+ }
+ return viewRoot == null ? null : viewRoot.getInputToken();
}
private void notifyImeHidden() {
- setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
- onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+ requestHideSelf(0);
+ }
+
+ private void removeImeSurface() {
+ if (!mShowInputRequested && !mWindowVisible) {
+ // hiding a window removes its surface.
+ mWindow.hide();
+ }
}
private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
}
+ /** Set region of the keyboard to be avoided from back gesture */
+ private void setImeExclusionRect(int visibleTopInsets) {
+ View inputFrameRootView = mInputFrame.getRootView();
+ Rect r = new Rect(0, visibleTopInsets, inputFrameRootView.getWidth(),
+ inputFrameRootView.getHeight());
+ inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r));
+ }
+
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
@@ -776,6 +962,14 @@ public class InputMethodService extends AbstractInputMethodService {
public final void notifyImeHidden() {
InputMethodService.this.notifyImeHidden();
}
+
+ /**
+ * Notify IME that surface can be now removed.
+ * @hide
+ */
+ public final void removeImeSurface() {
+ InputMethodService.this.removeImeSurface();
+ }
}
/**
@@ -997,6 +1191,11 @@ public class InputMethodService extends AbstractInputMethodService {
super.onCreate();
mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
mSettingsObserver = SettingsObserver.createAndRegister(this);
+
+ mIsAutomotive = isAutomotive();
+ mAutomotiveHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
+
// TODO(b/111364446) Need to address context lifecycle issue if need to re-create
// for update resources & configuration correctly when show soft input
// in non-default display.
@@ -1004,6 +1203,22 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
+ mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
+ mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
+ mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);
+
+ // IME layout should always be inset by navigation bar, no matter its current visibility,
+ // unless automotive requests it, since automotive may hide the navigation bar.
+ mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> v.onApplyWindowInsets(
+ new WindowInsets.Builder(insets).setInsets(
+ navigationBars(),
+ mIsAutomotive && mAutomotiveHideNavBarForKeyboard
+ ? android.graphics.Insets.NONE
+ : insets.getInsetsIgnoringVisibility(navigationBars())
+ )
+ .build()));
+
// For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
// by default (but IME developers can opt this out later if they want a new behavior).
mWindow.getWindow().setFlags(
@@ -1011,6 +1226,10 @@ public class InputMethodService extends AbstractInputMethodService {
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
+
+ mInlineSuggestionSessionController = new InlineSuggestionSessionController(
+ this::onCreateInlineSuggestionsRequest, this::getHostInputToken,
+ this::onInlineSuggestionsResponse);
}
/**
@@ -1220,8 +1439,9 @@ public class InputMethodService extends AbstractInputMethodService {
* screen orientation changes.
*/
public int getMaxWidth() {
- WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- return wm.getDefaultDisplay().getWidth();
+ final WindowManager windowManager = getSystemService(WindowManager.class);
+ return WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout(
+ windowManager.getCurrentWindowMetrics()).width();
}
/**
@@ -1853,6 +2073,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
final boolean result = onShowInputRequested(flags, configChange);
+ mInlineSuggestionSessionController.notifyOnShowInputRequested(result);
if (result) {
mShowInputFlags = flags;
} else {
@@ -1952,6 +2173,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (!mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
+ mInlineSuggestionSessionController.notifyOnStartInputView();
onStartInputView(mInputEditorInfo, false);
}
} else if (!mCandidatesViewStarted) {
@@ -1974,19 +2196,25 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when
- * pre-rendering is enabled.
+ * {@link ViewRootImpl.sNewInsetsMode} is enabled.
* @param setVisible {@code true} to make it visible, false to hide it.
*/
- private void applyVisibilityInInsetsConsumer(boolean setVisible) {
- if (!mIsPreRendered) {
+ private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) {
+ if (!isVisibilityAppliedUsingInsetsConsumer()) {
return;
}
- mPrivOps.applyImeVisibility(setVisible);
+ mPrivOps.applyImeVisibility(setVisible
+ ? mCurShowInputToken : mCurHideInputToken, setVisible);
+ }
+
+ private boolean isVisibilityAppliedUsingInsetsConsumer() {
+ return ViewRootImpl.sNewInsetsMode > NEW_INSETS_MODE_NONE;
}
private void finishViews(boolean finishingInput) {
if (mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
+ mInlineSuggestionSessionController.notifyOnFinishInputView();
onFinishInputView(finishingInput);
} else if (mCandidatesViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
@@ -2007,7 +2235,15 @@ public class InputMethodService extends AbstractInputMethodService {
mWindowVisible = false;
finishViews(false /* finishingInput */);
if (mDecorViewVisible) {
- mWindow.hide();
+ // When insets API is enabled, it is responsible for client and server side
+ // visibility of IME window.
+ if (isVisibilityAppliedUsingInsetsConsumer()) {
+ if (mInputView != null) {
+ mInputView.dispatchWindowVisibilityChanged(View.GONE);
+ }
+ } else {
+ mWindow.hide();
+ }
mDecorViewVisible = false;
onWindowHidden();
mDecorViewWasVisible = false;
@@ -2073,6 +2309,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
finishViews(true /* finishingInput */);
if (mInputStarted) {
+ mInlineSuggestionSessionController.notifyOnFinishInput();
if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
onFinishInput();
}
@@ -2089,12 +2326,16 @@ public class InputMethodService extends AbstractInputMethodService {
mStartedInputConnection = ic;
mInputEditorInfo = attribute;
initialize();
+ mInlineSuggestionSessionController.notifyOnStartInput(
+ attribute == null ? null : attribute.packageName,
+ attribute == null ? null : attribute.autofillId);
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);
if (mDecorViewVisible) {
if (mShowInputRequested) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
+ mInlineSuggestionSessionController.notifyOnStartInputView();
onStartInputView(mInputEditorInfo, restarting);
startExtractingText(true);
} else if (mCandidatesVisibility == View.VISIBLE) {
@@ -3032,6 +3273,11 @@ public class InputMethodService extends AbstractInputMethodService {
: IME_VISIBLE) : 0);
}
+ private boolean isAutomotive() {
+ return getApplicationContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
/**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index 31c948a14698..dbb669be1402 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IMultiClientInputMethodSession;
+import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.view.IInputContext;
@@ -46,7 +47,6 @@ import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputConnectionWrapper;
import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
@@ -80,19 +80,19 @@ final class MultiClientInputMethodClientCallbackAdaptor {
@Nullable
InputEventReceiver mInputEventReceiver;
- private final AtomicBoolean mFinished = new AtomicBoolean(false);
+ private final CancellationGroup mCancellationGroup = new CancellationGroup();
IInputMethodSession.Stub createIInputMethodSession() {
synchronized (mSessionLock) {
return new InputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
synchronized (mSessionLock) {
return new MultiClientInputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
@@ -105,7 +105,7 @@ final class MultiClientInputMethodClientCallbackAdaptor {
mHandler = new Handler(looper, null, true);
mReadChannel = readChannel;
mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
- mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback);
+ mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
}
}
@@ -139,16 +139,17 @@ final class MultiClientInputMethodClientCallbackAdaptor {
}
private static final class ImeInputEventReceiver extends InputEventReceiver {
- private final AtomicBoolean mFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
private final KeyEvent.DispatcherState mDispatcherState;
private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
- ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished,
+ ImeInputEventReceiver(InputChannel readChannel, Looper looper,
+ CancellationGroup cancellationGroupOnFinishSession,
KeyEvent.DispatcherState dispatcherState,
MultiClientInputMethodServiceDelegate.ClientCallback callback) {
super(readChannel, looper);
- mFinished = finished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
mDispatcherState = dispatcherState;
mClientCallback = callback;
mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
@@ -156,7 +157,7 @@ final class MultiClientInputMethodClientCallbackAdaptor {
@Override
public void onInputEvent(InputEvent event) {
- if (mFinished.get()) {
+ if (mCancellationGroupOnFinishSession.isCanceled()) {
// The session has been finished.
finishInputEvent(event, false);
return;
@@ -187,14 +188,14 @@ final class MultiClientInputMethodClientCallbackAdaptor {
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
- AtomicBoolean sessionFinished) {
+ CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -272,7 +273,7 @@ final class MultiClientInputMethodClientCallbackAdaptor {
if (mCallbackImpl == null || mHandler == null) {
return;
}
- mSessionFinished.set(true);
+ mCancellationGroupOnFinishSession.cancelAll();
mHandler.sendMessage(PooledLambda.obtainMessage(
CallbackImpl::finishSession, mCallbackImpl));
mCallbackImpl = null;
@@ -296,6 +297,12 @@ final class MultiClientInputMethodClientCallbackAdaptor {
// no-op for multi-session since IME is responsible controlling navigation bar buttons.
reportNotSupported();
}
+
+ @Override
+ public void removeImeSurface() {
+ // no-op for multi-session
+ reportNotSupported();
+ }
}
private static final class MultiClientInputMethodSessionImpl
@@ -305,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor {
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
- Handler handler, AtomicBoolean sessionFinished) {
+ Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -329,7 +336,7 @@ final class MultiClientInputMethodClientCallbackAdaptor {
new WeakReference<>(null);
args.arg1 = (inputContext == null) ? null
: new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
- mSessionFinished);
+ mCancellationGroupOnFinishSession);
args.arg2 = editorInfo;
args.argi1 = controlFlags;
args.argi2 = softInputMode;
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 356b3448430a..6efd03c44b9f 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -21,7 +21,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.app.Dialog;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -29,6 +28,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import java.lang.annotation.Retention;
@@ -51,7 +51,6 @@ public class SoftInputWindow extends Dialog {
final int mWindowType;
final int mGravity;
final boolean mTakesFocus;
- final boolean mAutomotiveHideNavBarForKeyboard;
private final Rect mBounds = new Rect();
@Retention(SOURCE)
@@ -96,6 +95,13 @@ public class SoftInputWindow extends Dialog {
lp.token = token;
getWindow().setAttributes(lp);
updateWindowState(SoftInputWindowState.TOKEN_SET);
+
+ // As soon as we have a token, make sure the window is added (but not shown) by
+ // setting visibility to INVISIBLE and calling show() on Dialog. Note that
+ // WindowInsetsController.OnControllableInsetsChangedListener relies on the window
+ // being added to function.
+ getWindow().getDecorView().setVisibility(View.INVISIBLE);
+ show();
return;
case SoftInputWindowState.TOKEN_SET:
case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
@@ -136,8 +142,6 @@ public class SoftInputWindow extends Dialog {
mWindowType = windowType;
mGravity = gravity;
mTakesFocus = takesFocus;
- mAutomotiveHideNavBarForKeyboard = context.getResources().getBoolean(
- com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
initDockWindow();
}
@@ -251,11 +255,6 @@ public class SoftInputWindow extends Dialog {
windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
- if (isAutomotive() && mAutomotiveHideNavBarForKeyboard) {
- windowSetFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
- windowModFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
- }
-
getWindow().setFlags(windowSetFlags, windowModFlags);
}
@@ -347,10 +346,6 @@ public class SoftInputWindow extends Dialog {
mWindowState = newState;
}
- private boolean isAutomotive() {
- return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- }
-
private static String stateToString(@SoftInputWindowState int state) {
switch (state) {
case SoftInputWindowState.TOKEN_PENDING: