diff options
Diffstat (limited to 'core/java/android')
3 files changed, 385 insertions, 172 deletions
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java index 9b3e8c9c137d..98ccbfc8aef4 100644 --- a/core/java/android/inputmethodservice/InlineSuggestionSession.java +++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java @@ -20,129 +20,148 @@ 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.content.ComponentName; +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.IInlineSuggestionsResponseCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; import java.lang.ref.WeakReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; /** - * Maintains an active inline suggestion session with the autofill manager service. + * 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> - * Each session corresponds to one {@link InlineSuggestionsRequest} and one {@link - * IInlineSuggestionsResponseCallback}, but there may be multiple invocations of the response - * callback for the same field or different fields in the same component. + * 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> - * The data flow from IMS point of view is: - * Before calling {@link InputMethodService#onStartInputView(EditorInfo, boolean)}, call the {@link - * #notifyOnStartInputView(AutofillId)} - * -> - * [async] {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView(AutofillId)} - * --- process boundary --- - * -> - * {@link com.android.server.inputmethod.InputMethodManagerService - * .InlineSuggestionsRequestCallbackDecorator#onInputMethodStartInputView(AutofillId)} - * -> - * {@link com.android.server.autofill.InlineSuggestionSession - * .InlineSuggestionsRequestCallbackImpl#onInputMethodStartInputView(AutofillId)} - * - * <p> - * The data flow for {@link #notifyOnFinishInputView(AutofillId)} is similar. + * <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"; - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); - @NonNull - private final ComponentName mComponentName; + private final Handler mMainThreadHandler; @NonNull - private final IInlineSuggestionsRequestCallback mCallback; - @NonNull - private final InlineSuggestionsResponseCallbackImpl mResponseCallback; + private final InlineSuggestionSessionController mInlineSuggestionSessionController; @NonNull - private final Supplier<String> mClientPackageNameSupplier; + private final InlineSuggestionsRequestInfo mRequestInfo; @NonNull - private final Supplier<AutofillId> mClientAutofillIdSupplier; + private final IInlineSuggestionsRequestCallback mCallback; @NonNull - private final Supplier<InlineSuggestionsRequest> mRequestSupplier; + private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier; @NonNull private final Supplier<IBinder> mHostInputTokenSupplier; @NonNull private final Consumer<InlineSuggestionsResponse> mResponseConsumer; - private volatile boolean mInvalidated = false; - InlineSuggestionSession(@NonNull ComponentName componentName, + /** + * 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 Supplier<String> clientPackageNameSupplier, - @NonNull Supplier<AutofillId> clientAutofillIdSupplier, - @NonNull Supplier<InlineSuggestionsRequest> requestSupplier, + @NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier, @NonNull Supplier<IBinder> hostInputTokenSupplier, @NonNull Consumer<InlineSuggestionsResponse> responseConsumer, - boolean inputViewStarted) { - mComponentName = componentName; + @NonNull InlineSuggestionSessionController inlineSuggestionSessionController, + @NonNull Handler mainThreadHandler) { + mRequestInfo = requestInfo; mCallback = callback; - mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this); - mClientPackageNameSupplier = clientPackageNameSupplier; - mClientAutofillIdSupplier = clientAutofillIdSupplier; mRequestSupplier = requestSupplier; mHostInputTokenSupplier = hostInputTokenSupplier; mResponseConsumer = responseConsumer; + mInlineSuggestionSessionController = inlineSuggestionSessionController; + mMainThreadHandler = mainThreadHandler; + } - makeInlineSuggestionsRequest(inputViewStarted); + @MainThread + InlineSuggestionsRequestInfo getRequestInfo() { + return mRequestInfo; } - void notifyOnStartInputView(AutofillId imeFieldId) { - if (DEBUG) Log.d(TAG, "notifyOnStartInputView"); - try { - mCallback.onInputMethodStartInputView(imeFieldId); - } catch (RemoteException e) { - Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e); - } + @MainThread + IInlineSuggestionsRequestCallback getRequestCallback() { + return mCallback; } - void notifyOnFinishInputView(AutofillId imeFieldId) { - if (DEBUG) Log.d(TAG, "notifyOnFinishInputView"); - try { - mCallback.onInputMethodFinishInputView(imeFieldId); - } catch (RemoteException e) { - Log.w(TAG, "onInputMethodFinishInputView() remote exception:" + e); - } + /** + * 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; } /** - * This needs to be called before creating a new session, such that the later response callbacks - * will be discarded. + * 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. */ - void invalidateSession() { - mInvalidated = true; + @MainThread + boolean isCallbackInvoked() { + return mCallbackInvoked; } /** - * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current - * Autofill Session through - * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}. + * 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}. */ - private void makeInlineSuggestionsRequest(boolean inputViewStarted) { + @MainThread + void invalidate() { + if (mResponseCallback != null) { + 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.get(); + final InlineSuggestionsRequest request = mRequestSupplier.apply( + mRequestInfo.getUiExtras()); if (request == null) { if (DEBUG) { Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request"); @@ -150,37 +169,19 @@ class InlineSuggestionSession { mCallback.onInlineSuggestionsUnsupported(); } else { request.setHostInputToken(mHostInputTokenSupplier.get()); - mCallback.onInlineSuggestionsRequest(request, mResponseCallback, - mClientAutofillIdSupplier.get(), inputViewStarted); + mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this); + mCallback.onInlineSuggestionsRequest(request, mResponseCallback); } } catch (RemoteException e) { Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e); } + mCallbackInvoked = true; } - private void handleOnInlineSuggestionsResponse(@NonNull AutofillId fieldId, + @MainThread + void handleOnInlineSuggestionsResponse(@NonNull AutofillId fieldId, @NonNull InlineSuggestionsResponse response) { - if (mInvalidated) { - if (DEBUG) { - Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session"); - } - return; - } - // 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. - final AutofillId imeClientFieldId = mClientAutofillIdSupplier.get(); - if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get()) - || imeClientFieldId == null - || fieldId.getViewId() != imeClientFieldId.getViewId()) { - if (DEBUG) { - Log.d(TAG, - "handleOnInlineSuggestionsResponse() called on the wrong package/field " - + "name: " + mComponentName.getPackageName() + " v.s. " - + mClientPackageNameSupplier.get() + ", " + fieldId + " v.s. " - + mClientAutofillIdSupplier.get()); - } + if (!mInlineSuggestionSessionController.match(fieldId)) { return; } if (DEBUG) { @@ -192,23 +193,31 @@ class InlineSuggestionSession { /** * Internal implementation of {@link IInlineSuggestionsResponseCallback}. */ - static final class InlineSuggestionsResponseCallbackImpl - extends IInlineSuggestionsResponseCallback.Stub { - private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession; + 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); + } - private InlineSuggestionsResponseCallbackImpl( - InlineSuggestionSession inlineSuggestionSession) { - mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession); + void invalidate() { + mInvalid = true; } + @BinderThread @Override public void onInlineSuggestionsResponse(AutofillId fieldId, InlineSuggestionsResponse response) { - final InlineSuggestionSession session = mInlineSuggestionSession.get(); + if (mInvalid) { + return; + } + final InlineSuggestionSession session = mSession.get(); if (session != null) { - session.mHandler.sendMessage(obtainMessage( - InlineSuggestionSession::handleOnInlineSuggestionsResponse, session, - fieldId, response)); + 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..c9f9059bed4f --- /dev/null +++ b/core/java/android/inputmethodservice/InlineSuggestionSessionController.java @@ -0,0 +1,261 @@ +/* + * 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) { + // 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 782fff2f69e0..93ce88b767bc 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -49,7 +49,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; import android.provider.Settings; @@ -74,7 +73,6 @@ import android.view.WindowInsets; import android.view.WindowInsets.Side; 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; @@ -445,18 +443,7 @@ public class InputMethodService extends AbstractInputMethodService { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; - /** - * We use a separate {@code mInlineLock} to make sure {@code mInlineSuggestionSession} is - * only accessed synchronously. Although when the lock is introduced, all the calls are from - * the main thread so the lock is not really necessarily (but for the same reason it also - * doesn't hurt), it's still being added as a safety guard to make sure in the future we - * don't add more code causing race condition when updating the {@code - * mInlineSuggestionSession}. - */ - private final Object mInlineLock = new Object(); - @GuardedBy("mInlineLock") - @Nullable - private InlineSuggestionSession mInlineSuggestionSession; + private InlineSuggestionSessionController mInlineSuggestionSessionController; private boolean mAutomotiveHideNavBarForKeyboard; private boolean mIsAutomotive; @@ -554,7 +541,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) { Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()"); } - handleOnCreateInlineSuggestionsRequest(requestInfo, cb); + mInlineSuggestionSessionController.onMakeInlineSuggestionsRequest(requestInfo, cb); } /** @@ -823,47 +810,6 @@ public class InputMethodService extends AbstractInputMethodService { return false; } - @MainThread - private void handleOnCreateInlineSuggestionsRequest( - @NonNull InlineSuggestionsRequestInfo requestInfo, - @NonNull IInlineSuggestionsRequestCallback callback) { - if (!mInputStarted) { - try { - Log.w(TAG, "onStartInput() not called yet"); - callback.onInlineSuggestionsUnsupported(); - } catch (RemoteException e) { - Log.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e); - } - return; - } - - synchronized (mInlineLock) { - if (mInlineSuggestionSession != null) { - mInlineSuggestionSession.invalidateSession(); - } - mInlineSuggestionSession = new InlineSuggestionSession(requestInfo.getComponentName(), - callback, this::getEditorInfoPackageName, this::getEditorInfoAutofillId, - () -> onCreateInlineSuggestionsRequest(requestInfo.getUiExtras()), - this::getHostInputToken, this::onInlineSuggestionsResponse, mInputViewStarted); - } - } - - @Nullable - private String getEditorInfoPackageName() { - if (mInputEditorInfo != null) { - return mInputEditorInfo.packageName; - } - return null; - } - - @Nullable - private AutofillId getEditorInfoAutofillId() { - if (mInputEditorInfo != null) { - return mInputEditorInfo.autofillId; - } - return null; - } - /** * Returns the {@link IBinder} input token from the host view root. */ @@ -1269,6 +1215,10 @@ public class InputMethodService extends AbstractInputMethodService { initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); + + mInlineSuggestionSessionController = new InlineSuggestionSessionController( + this::onCreateInlineSuggestionsRequest, this::getHostInputToken, + this::onInlineSuggestionsResponse); } /** @@ -2112,6 +2062,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 { @@ -2211,11 +2162,7 @@ public class InputMethodService extends AbstractInputMethodService { if (!mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; - synchronized (mInlineLock) { - if (mInlineSuggestionSession != null) { - mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId()); - } - } + mInlineSuggestionSessionController.notifyOnStartInputView(); onStartInputView(mInputEditorInfo, false); } } else if (!mCandidatesViewStarted) { @@ -2256,11 +2203,7 @@ public class InputMethodService extends AbstractInputMethodService { private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - synchronized (mInlineLock) { - if (mInlineSuggestionSession != null) { - mInlineSuggestionSession.notifyOnFinishInputView(getEditorInfoAutofillId()); - } - } + mInlineSuggestionSessionController.notifyOnFinishInputView(); onFinishInputView(finishingInput); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); @@ -2355,6 +2298,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(); } @@ -2371,17 +2315,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; - synchronized (mInlineLock) { - if (mInlineSuggestionSession != null) { - mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId()); - } - } + mInlineSuggestionSessionController.notifyOnStartInputView(); onStartInputView(mInputEditorInfo, restarting); startExtractingText(true); } else if (mCandidatesVisibility == View.VISIBLE) { |
