From abd6b072ff174f46990ae07a38d6a444e475f832 Mon Sep 17 00:00:00 2001 From: Feng Cao Date: Wed, 12 Feb 2020 19:08:44 -0800 Subject: [PATCH] Refactor the InputMethodService to move inline suggestion stuff to a separate class Test: manual Bug: 149442582 Change-Id: I71a3cf2ddd24dcf79709ec89136ad7609089f54c --- .../InlineSuggestionSession.java | 158 ++++++++++++++++++ .../InputMethodService.java | 129 ++------------ 2 files changed, 173 insertions(+), 114 deletions(-) create mode 100644 core/java/android/inputmethodservice/InlineSuggestionSession.java diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java new file mode 100644 index 0000000000000..edae06a0b9dfb --- /dev/null +++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java @@ -0,0 +1,158 @@ +/* + * 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.NonNull; +import android.content.ComponentName; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; + +import java.lang.ref.WeakReference; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Maintains an active inline suggestion session. + * + *

+ * Each session corresponds to one inline suggestion request, but there may be multiple callbacks + * with the inline suggestions response. + */ +class InlineSuggestionSession { + + private static final String TAG = InlineSuggestionSession.class.getSimpleName(); + + private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); + + @NonNull + private final ComponentName mComponentName; + @NonNull + private final IInlineSuggestionsRequestCallback mCallback; + @NonNull + private final InlineSuggestionsResponseCallbackImpl mResponseCallback; + @NonNull + private final Supplier mClientPackageNameSupplier; + @NonNull + private final Supplier mRequestSupplier; + @NonNull + private final Supplier mHostInputTokenSupplier; + @NonNull + private final Consumer mResponseConsumer; + + private volatile boolean mInvalidated = false; + + InlineSuggestionSession(@NonNull ComponentName componentName, + @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull Supplier clientPackageNameSupplier, + @NonNull Supplier requestSupplier, + @NonNull Supplier hostInputTokenSupplier, + @NonNull Consumer responseConsumer) { + mComponentName = componentName; + mCallback = callback; + mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this); + mClientPackageNameSupplier = clientPackageNameSupplier; + mRequestSupplier = requestSupplier; + mHostInputTokenSupplier = hostInputTokenSupplier; + mResponseConsumer = responseConsumer; + + makeInlineSuggestionsRequest(); + } + + /** + * This needs to be called before creating a new session, such that the later response callbacks + * will be discarded. + */ + void invalidateSession() { + mInvalidated = true; + } + + /** + * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current + * Autofill Session through + * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}. + */ + private void makeInlineSuggestionsRequest() { + try { + final InlineSuggestionsRequest request = mRequestSupplier.get(); + if (request == null) { + if (DEBUG) { + Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request"); + } + mCallback.onInlineSuggestionsUnsupported(); + } else { + request.setHostInputToken(mHostInputTokenSupplier.get()); + mCallback.onInlineSuggestionsRequest(request, mResponseCallback); + } + } catch (RemoteException e) { + Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e); + } + } + + private void handleOnInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) { + if (mInvalidated) { + if (DEBUG) { + Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session"); + } + return; + } + // TODO(b/149522488): checking the current focused input field to make sure we don't send + // inline responses for previous input field + if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())) { + if (DEBUG) { + Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package name"); + } + return; + } + mResponseConsumer.accept(response); + } + + /** + * Internal implementation of {@link IInlineSuggestionsResponseCallback}. + */ + static final class InlineSuggestionsResponseCallbackImpl + extends IInlineSuggestionsResponseCallback.Stub { + private final WeakReference mInlineSuggestionSession; + + private InlineSuggestionsResponseCallbackImpl( + InlineSuggestionSession inlineSuggestionSession) { + mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession); + } + + @Override + public void onInlineSuggestionsResponse(InlineSuggestionsResponse response) + throws RemoteException { + final InlineSuggestionSession session = mInlineSuggestionSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + InlineSuggestionSession::handleOnInlineSuggestionsResponse, session, + response)); + } + } + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index a95938b5db2d9..b1aa67e9f7ed0 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -22,8 +22,6 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowInsets.Type.navigationBars; 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; @@ -51,7 +49,6 @@ 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; @@ -102,13 +99,11 @@ 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; /** @@ -450,7 +445,7 @@ public class InputMethodService extends AbstractInputMethodService { final int[] mTmpLocation = new int[2]; @Nullable - private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null; + private InlineSuggestionSession mInlineSuggestionSession; private boolean mAutomotiveHideNavBarForKeyboard; private boolean mIsAutomotive; @@ -465,8 +460,6 @@ public class InputMethodService extends AbstractInputMethodService { */ private IBinder mCurShowInputToken; - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); - final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { onComputeInsets(mTmpInsets); if (isExtractViewShown()) { @@ -538,7 +531,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) { Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()"); } - handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb); + handleOnCreateInlineSuggestionsRequest(componentName, cb); } /** @@ -770,40 +763,9 @@ public class InputMethodService extends AbstractInputMethodService { return false; } - /** - * 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 { - request.setHostInputToken(getHostInputToken()); - final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - new InlineSuggestionsResponseCallbackImpl(this, - mInlineSuggestionsRequestInfo.mComponentName, - mInlineSuggestionsRequestInfo.mFocusedId); - requestCallback.onInlineSuggestionsRequest(request, - inlineSuggestionsResponseCallback); - } - } catch (RemoteException e) { - Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e); - } - } - + @MainThread private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName, - @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) { + @NonNull IInlineSuggestionsRequestCallback callback) { if (!mInputStarted) { try { Log.w(TAG, "onStartInput() not called yet"); @@ -814,24 +776,20 @@ public class InputMethodService extends AbstractInputMethodService { return; } - mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId, - callback); - - makeInlineSuggestionsRequest(); + if (mInlineSuggestionSession != null) { + mInlineSuggestionSession.invalidateSession(); + } + mInlineSuggestionSession = new InlineSuggestionSession(componentName, callback, + this::getEditorInfoPackageName, this::onCreateInlineSuggestionsRequest, + this::getHostInputToken, this::onInlineSuggestionsResponse); } - private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName, - @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) { - if (!mInlineSuggestionsRequestInfo.validate(componentName)) { - if (DEBUG) { - Log.d(TAG, - "Response component=" + componentName + " differs from request component=" - + mInlineSuggestionsRequestInfo.mComponentName - + ", ignoring response"); - } - return; + @Nullable + private String getEditorInfoPackageName() { + if (mInputEditorInfo != null) { + return mInputEditorInfo.packageName; } - onInlineSuggestionsResponse(response); + return null; } /** @@ -863,63 +821,6 @@ public class InputMethodService extends AbstractInputMethodService { inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r)); } - /** - * Internal implementation of {@link IInlineSuggestionsResponseCallback}. - */ - private static final class InlineSuggestionsResponseCallbackImpl - extends IInlineSuggestionsResponseCallback.Stub { - private final WeakReference 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 (DEBUG && !result) { - Log.d(TAG, "Cached request info ComponentName=" + mComponentName - + " differs from received ComponentName=" + componentName); - } - return result; - } - } - /** * Concrete implementation of * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides