Merge "Send more IME events to autofill manager service." into rvc-dev am: cbd80ea094 am: e8a3b8d44b
Change-Id: Ie596334e58682c51a642a8cf4ac707a34363304b
This commit is contained in:
@@ -20,129 +20,148 @@ import static android.inputmethodservice.InputMethodService.DEBUG;
|
|||||||
|
|
||||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
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.NonNull;
|
||||||
import android.content.ComponentName;
|
import android.annotation.Nullable;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.autofill.AutofillId;
|
import android.view.autofill.AutofillId;
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||||
import android.view.inputmethod.InlineSuggestionsResponse;
|
import android.view.inputmethod.InlineSuggestionsResponse;
|
||||||
|
|
||||||
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
||||||
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
||||||
|
import com.android.internal.view.InlineSuggestionsRequestInfo;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
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>
|
* <p>
|
||||||
* Each session corresponds to one {@link InlineSuggestionsRequest} and one {@link
|
* TODO(b/151123764): currently the session may receive responses for different views on the same
|
||||||
* IInlineSuggestionsResponseCallback}, but there may be multiple invocations of the response
|
* screen, but we will fix it so each session corresponds to one view.
|
||||||
* callback for the same field or different fields in the same component.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p> All the methods are expected to be called from the main thread, to ensure thread safety.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
class InlineSuggestionSession {
|
class InlineSuggestionSession {
|
||||||
|
|
||||||
private static final String TAG = "ImsInlineSuggestionSession";
|
private static final String TAG = "ImsInlineSuggestionSession";
|
||||||
|
|
||||||
private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ComponentName mComponentName;
|
private final Handler mMainThreadHandler;
|
||||||
|
@NonNull
|
||||||
|
private final InlineSuggestionSessionController mInlineSuggestionSessionController;
|
||||||
|
@NonNull
|
||||||
|
private final InlineSuggestionsRequestInfo mRequestInfo;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IInlineSuggestionsRequestCallback mCallback;
|
private final IInlineSuggestionsRequestCallback mCallback;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final InlineSuggestionsResponseCallbackImpl mResponseCallback;
|
private final Function<Bundle, InlineSuggestionsRequest> mRequestSupplier;
|
||||||
@NonNull
|
|
||||||
private final Supplier<String> mClientPackageNameSupplier;
|
|
||||||
@NonNull
|
|
||||||
private final Supplier<AutofillId> mClientAutofillIdSupplier;
|
|
||||||
@NonNull
|
|
||||||
private final Supplier<InlineSuggestionsRequest> mRequestSupplier;
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Supplier<IBinder> mHostInputTokenSupplier;
|
private final Supplier<IBinder> mHostInputTokenSupplier;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
|
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 IInlineSuggestionsRequestCallback callback,
|
||||||
@NonNull Supplier<String> clientPackageNameSupplier,
|
@NonNull Function<Bundle, InlineSuggestionsRequest> requestSupplier,
|
||||||
@NonNull Supplier<AutofillId> clientAutofillIdSupplier,
|
|
||||||
@NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
|
|
||||||
@NonNull Supplier<IBinder> hostInputTokenSupplier,
|
@NonNull Supplier<IBinder> hostInputTokenSupplier,
|
||||||
@NonNull Consumer<InlineSuggestionsResponse> responseConsumer,
|
@NonNull Consumer<InlineSuggestionsResponse> responseConsumer,
|
||||||
boolean inputViewStarted) {
|
@NonNull InlineSuggestionSessionController inlineSuggestionSessionController,
|
||||||
mComponentName = componentName;
|
@NonNull Handler mainThreadHandler) {
|
||||||
|
mRequestInfo = requestInfo;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
|
|
||||||
mClientPackageNameSupplier = clientPackageNameSupplier;
|
|
||||||
mClientAutofillIdSupplier = clientAutofillIdSupplier;
|
|
||||||
mRequestSupplier = requestSupplier;
|
mRequestSupplier = requestSupplier;
|
||||||
mHostInputTokenSupplier = hostInputTokenSupplier;
|
mHostInputTokenSupplier = hostInputTokenSupplier;
|
||||||
mResponseConsumer = responseConsumer;
|
mResponseConsumer = responseConsumer;
|
||||||
|
mInlineSuggestionSessionController = inlineSuggestionSessionController;
|
||||||
makeInlineSuggestionsRequest(inputViewStarted);
|
mMainThreadHandler = mainThreadHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyOnStartInputView(AutofillId imeFieldId) {
|
@MainThread
|
||||||
if (DEBUG) Log.d(TAG, "notifyOnStartInputView");
|
InlineSuggestionsRequestInfo getRequestInfo() {
|
||||||
try {
|
return mRequestInfo;
|
||||||
mCallback.onInputMethodStartInputView(imeFieldId);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(TAG, "onInputMethodStartInputView() remote exception:" + e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyOnFinishInputView(AutofillId imeFieldId) {
|
@MainThread
|
||||||
if (DEBUG) Log.d(TAG, "notifyOnFinishInputView");
|
IInlineSuggestionsRequestCallback getRequestCallback() {
|
||||||
try {
|
return mCallback;
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
if (mResponseCallback != null) {
|
||||||
|
mResponseCallback.invalidate();
|
||||||
|
mResponseCallback = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This needs to be called before creating a new session, such that the later response callbacks
|
* Gets the {@link InlineSuggestionsRequest} from IME and send it back to the Autofill if it's
|
||||||
* will be discarded.
|
* not null.
|
||||||
|
*
|
||||||
|
* <p>Calling this method implies that the input is started on the view corresponding to the
|
||||||
|
* session.
|
||||||
*/
|
*/
|
||||||
void invalidateSession() {
|
@MainThread
|
||||||
mInvalidated = true;
|
void makeInlineSuggestionRequestUncheck() {
|
||||||
}
|
if (mCallbackInvoked) {
|
||||||
|
return;
|
||||||
/**
|
}
|
||||||
* Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current
|
|
||||||
* Autofill Session through
|
|
||||||
* {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
|
|
||||||
*/
|
|
||||||
private void makeInlineSuggestionsRequest(boolean inputViewStarted) {
|
|
||||||
try {
|
try {
|
||||||
final InlineSuggestionsRequest request = mRequestSupplier.get();
|
final InlineSuggestionsRequest request = mRequestSupplier.apply(
|
||||||
|
mRequestInfo.getUiExtras());
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
|
Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
|
||||||
@@ -150,37 +169,19 @@ class InlineSuggestionSession {
|
|||||||
mCallback.onInlineSuggestionsUnsupported();
|
mCallback.onInlineSuggestionsUnsupported();
|
||||||
} else {
|
} else {
|
||||||
request.setHostInputToken(mHostInputTokenSupplier.get());
|
request.setHostInputToken(mHostInputTokenSupplier.get());
|
||||||
mCallback.onInlineSuggestionsRequest(request, mResponseCallback,
|
mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
|
||||||
mClientAutofillIdSupplier.get(), inputViewStarted);
|
mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + 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) {
|
@NonNull InlineSuggestionsResponse response) {
|
||||||
if (mInvalidated) {
|
if (!mInlineSuggestionSessionController.match(fieldId)) {
|
||||||
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());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -192,23 +193,31 @@ class InlineSuggestionSession {
|
|||||||
/**
|
/**
|
||||||
* Internal implementation of {@link IInlineSuggestionsResponseCallback}.
|
* Internal implementation of {@link IInlineSuggestionsResponseCallback}.
|
||||||
*/
|
*/
|
||||||
static final class InlineSuggestionsResponseCallbackImpl
|
private static final class InlineSuggestionsResponseCallbackImpl extends
|
||||||
extends IInlineSuggestionsResponseCallback.Stub {
|
IInlineSuggestionsResponseCallback.Stub {
|
||||||
private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession;
|
private final WeakReference<InlineSuggestionSession> mSession;
|
||||||
|
private volatile boolean mInvalid = false;
|
||||||
|
|
||||||
private InlineSuggestionsResponseCallbackImpl(
|
private InlineSuggestionsResponseCallbackImpl(InlineSuggestionSession session) {
|
||||||
InlineSuggestionSession inlineSuggestionSession) {
|
mSession = new WeakReference<>(session);
|
||||||
mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void invalidate() {
|
||||||
|
mInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
@Override
|
@Override
|
||||||
public void onInlineSuggestionsResponse(AutofillId fieldId,
|
public void onInlineSuggestionsResponse(AutofillId fieldId,
|
||||||
InlineSuggestionsResponse response) {
|
InlineSuggestionsResponse response) {
|
||||||
final InlineSuggestionSession session = mInlineSuggestionSession.get();
|
if (mInvalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final InlineSuggestionSession session = mSession.get();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.mHandler.sendMessage(obtainMessage(
|
session.mMainThreadHandler.sendMessage(
|
||||||
InlineSuggestionSession::handleOnInlineSuggestionsResponse, session,
|
obtainMessage(InlineSuggestionSession::handleOnInlineSuggestionsResponse,
|
||||||
fieldId, response));
|
session, fieldId, response));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,6 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -74,7 +73,6 @@ import android.view.WindowInsets;
|
|||||||
import android.view.WindowInsets.Side;
|
import android.view.WindowInsets.Side;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.view.autofill.AutofillId;
|
|
||||||
import android.view.inputmethod.CompletionInfo;
|
import android.view.inputmethod.CompletionInfo;
|
||||||
import android.view.inputmethod.CursorAnchorInfo;
|
import android.view.inputmethod.CursorAnchorInfo;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
@@ -445,18 +443,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
final Insets mTmpInsets = new Insets();
|
final Insets mTmpInsets = new Insets();
|
||||||
final int[] mTmpLocation = new int[2];
|
final int[] mTmpLocation = new int[2];
|
||||||
|
|
||||||
/**
|
private InlineSuggestionSessionController mInlineSuggestionSessionController;
|
||||||
* 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 boolean mAutomotiveHideNavBarForKeyboard;
|
private boolean mAutomotiveHideNavBarForKeyboard;
|
||||||
private boolean mIsAutomotive;
|
private boolean mIsAutomotive;
|
||||||
@@ -554,7 +541,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
|
Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
|
||||||
}
|
}
|
||||||
handleOnCreateInlineSuggestionsRequest(requestInfo, cb);
|
mInlineSuggestionSessionController.onMakeInlineSuggestionsRequest(requestInfo, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -823,47 +810,6 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
return false;
|
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.
|
* Returns the {@link IBinder} input token from the host view root.
|
||||||
*/
|
*/
|
||||||
@@ -1269,6 +1215,10 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
|
|
||||||
initViews();
|
initViews();
|
||||||
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
|
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) {
|
private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
|
||||||
final boolean result = onShowInputRequested(flags, configChange);
|
final boolean result = onShowInputRequested(flags, configChange);
|
||||||
|
mInlineSuggestionSessionController.notifyOnShowInputRequested(result);
|
||||||
if (result) {
|
if (result) {
|
||||||
mShowInputFlags = flags;
|
mShowInputFlags = flags;
|
||||||
} else {
|
} else {
|
||||||
@@ -2211,11 +2162,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
if (!mInputViewStarted) {
|
if (!mInputViewStarted) {
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
|
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
|
||||||
mInputViewStarted = true;
|
mInputViewStarted = true;
|
||||||
synchronized (mInlineLock) {
|
mInlineSuggestionSessionController.notifyOnStartInputView();
|
||||||
if (mInlineSuggestionSession != null) {
|
|
||||||
mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onStartInputView(mInputEditorInfo, false);
|
onStartInputView(mInputEditorInfo, false);
|
||||||
}
|
}
|
||||||
} else if (!mCandidatesViewStarted) {
|
} else if (!mCandidatesViewStarted) {
|
||||||
@@ -2256,11 +2203,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
private void finishViews(boolean finishingInput) {
|
private void finishViews(boolean finishingInput) {
|
||||||
if (mInputViewStarted) {
|
if (mInputViewStarted) {
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
|
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
|
||||||
synchronized (mInlineLock) {
|
mInlineSuggestionSessionController.notifyOnFinishInputView();
|
||||||
if (mInlineSuggestionSession != null) {
|
|
||||||
mInlineSuggestionSession.notifyOnFinishInputView(getEditorInfoAutofillId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onFinishInputView(finishingInput);
|
onFinishInputView(finishingInput);
|
||||||
} else if (mCandidatesViewStarted) {
|
} else if (mCandidatesViewStarted) {
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
|
if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
|
||||||
@@ -2355,6 +2298,7 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
|
if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
|
||||||
finishViews(true /* finishingInput */);
|
finishViews(true /* finishingInput */);
|
||||||
if (mInputStarted) {
|
if (mInputStarted) {
|
||||||
|
mInlineSuggestionSessionController.notifyOnFinishInput();
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
|
if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
|
||||||
onFinishInput();
|
onFinishInput();
|
||||||
}
|
}
|
||||||
@@ -2371,17 +2315,16 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
mStartedInputConnection = ic;
|
mStartedInputConnection = ic;
|
||||||
mInputEditorInfo = attribute;
|
mInputEditorInfo = attribute;
|
||||||
initialize();
|
initialize();
|
||||||
|
mInlineSuggestionSessionController.notifyOnStartInput(
|
||||||
|
attribute == null ? null : attribute.packageName,
|
||||||
|
attribute == null ? null : attribute.autofillId);
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
|
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
|
||||||
onStartInput(attribute, restarting);
|
onStartInput(attribute, restarting);
|
||||||
if (mDecorViewVisible) {
|
if (mDecorViewVisible) {
|
||||||
if (mShowInputRequested) {
|
if (mShowInputRequested) {
|
||||||
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
|
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
|
||||||
mInputViewStarted = true;
|
mInputViewStarted = true;
|
||||||
synchronized (mInlineLock) {
|
mInlineSuggestionSessionController.notifyOnStartInputView();
|
||||||
if (mInlineSuggestionSession != null) {
|
|
||||||
mInlineSuggestionSession.notifyOnStartInputView(getEditorInfoAutofillId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onStartInputView(mInputEditorInfo, restarting);
|
onStartInputView(mInputEditorInfo, restarting);
|
||||||
startExtractingText(true);
|
startExtractingText(true);
|
||||||
} else if (mCandidatesVisibility == View.VISIBLE) {
|
} else if (mCandidatesVisibility == View.VISIBLE) {
|
||||||
|
|||||||
@@ -26,10 +26,36 @@ import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
|||||||
* {@hide}
|
* {@hide}
|
||||||
*/
|
*/
|
||||||
oneway interface IInlineSuggestionsRequestCallback {
|
oneway interface IInlineSuggestionsRequestCallback {
|
||||||
|
// Indicates that the current IME does not support inline suggestion.
|
||||||
void onInlineSuggestionsUnsupported();
|
void onInlineSuggestionsUnsupported();
|
||||||
|
|
||||||
|
// Sends the inline suggestions request from IME to Autofill. Calling this method indicates
|
||||||
|
// that the IME input is started on the view corresponding to the request.
|
||||||
void onInlineSuggestionsRequest(in InlineSuggestionsRequest request,
|
void onInlineSuggestionsRequest(in InlineSuggestionsRequest request,
|
||||||
in IInlineSuggestionsResponseCallback callback, in AutofillId imeFieldId,
|
in IInlineSuggestionsResponseCallback callback);
|
||||||
boolean inputViewStarted);
|
|
||||||
void onInputMethodStartInputView(in AutofillId imeFieldId);
|
// Signals that {@link android.inputmethodservice.InputMethodService
|
||||||
void onInputMethodFinishInputView(in AutofillId imeFieldId);
|
// #onStartInput(EditorInfo, boolean)} is called on the given focused field.
|
||||||
|
void onInputMethodStartInput(in AutofillId imeFieldId);
|
||||||
|
|
||||||
|
// Signals that {@link android.inputmethodservice.InputMethodService
|
||||||
|
// #dispatchOnShowInputRequested(int, boolean)} is called and shares the call result.
|
||||||
|
// The true value of {@code requestResult} means the IME is about to be shown, while
|
||||||
|
// false value means the IME will not be shown.
|
||||||
|
void onInputMethodShowInputRequested(boolean requestResult);
|
||||||
|
|
||||||
|
// Signals that {@link android.inputmethodservice.InputMethodService
|
||||||
|
// #onStartInputView(EditorInfo, boolean)} is called on the field specified by the earlier
|
||||||
|
// {@link #onInputMethodStartInput(AutofillId)}.
|
||||||
|
void onInputMethodStartInputView();
|
||||||
|
|
||||||
|
// Signals that {@link android.inputmethodservice.InputMethodService
|
||||||
|
// #onFinishInputView(boolean)} is called on the field specified by the earlier
|
||||||
|
// {@link #onInputMethodStartInput(AutofillId)}.
|
||||||
|
void onInputMethodFinishInputView();
|
||||||
|
|
||||||
|
// Signals that {@link android.inputmethodservice.InputMethodService
|
||||||
|
// #onFinishInput()} is called on the field specified by the earlier
|
||||||
|
// {@link #onInputMethodStartInput(AutofillId)}.
|
||||||
|
void onInputMethodFinishInput();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,9 +113,16 @@ final class InlineSuggestionSession {
|
|||||||
mLock = lock;
|
mLock = lock;
|
||||||
mImeStatusListener = new ImeStatusListener() {
|
mImeStatusListener = new ImeStatusListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodStartInputView(AutofillId imeFieldId) {
|
public void onInputMethodStartInput(AutofillId imeFieldId) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mImeFieldId = imeFieldId;
|
mImeFieldId = imeFieldId;
|
||||||
|
mImeInputViewStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodStartInputView() {
|
||||||
|
synchronized (mLock) {
|
||||||
mImeInputViewStarted = true;
|
mImeInputViewStarted = true;
|
||||||
AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
|
AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
|
||||||
if (pendingAutofillResponse != null
|
if (pendingAutofillResponse != null
|
||||||
@@ -129,12 +136,18 @@ final class InlineSuggestionSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodFinishInputView(AutofillId imeFieldId) {
|
public void onInputMethodFinishInputView() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mImeFieldId = imeFieldId;
|
|
||||||
mImeInputViewStarted = false;
|
mImeInputViewStarted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInput() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mImeFieldId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +168,7 @@ final class InlineSuggestionSession {
|
|||||||
mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
|
mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
|
||||||
mUserId,
|
mUserId,
|
||||||
new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
|
new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
|
||||||
new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse,
|
new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
|
||||||
mImeStatusListener, requestConsumer, mHandler, mLock));
|
mImeStatusListener, requestConsumer, mHandler, mLock));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +257,7 @@ final class InlineSuggestionSession {
|
|||||||
extends IInlineSuggestionsRequestCallback.Stub {
|
extends IInlineSuggestionsRequestCallback.Stub {
|
||||||
|
|
||||||
private final Object mLock;
|
private final Object mLock;
|
||||||
|
private final AutofillId mAutofillId;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final CompletableFuture<ImeResponse> mResponse;
|
private final CompletableFuture<ImeResponse> mResponse;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@@ -252,10 +266,12 @@ final class InlineSuggestionSession {
|
|||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final Runnable mTimeoutCallback;
|
private final Runnable mTimeoutCallback;
|
||||||
|
|
||||||
private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response,
|
private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
|
||||||
|
CompletableFuture<ImeResponse> response,
|
||||||
ImeStatusListener imeStatusListener,
|
ImeStatusListener imeStatusListener,
|
||||||
Consumer<InlineSuggestionsRequest> requestConsumer,
|
Consumer<InlineSuggestionsRequest> requestConsumer,
|
||||||
Handler handler, Object lock) {
|
Handler handler, Object lock) {
|
||||||
|
mAutofillId = autofillId;
|
||||||
mResponse = response;
|
mResponse = response;
|
||||||
mImeStatusListener = imeStatusListener;
|
mImeStatusListener = imeStatusListener;
|
||||||
mRequestConsumer = requestConsumer;
|
mRequestConsumer = requestConsumer;
|
||||||
@@ -290,18 +306,9 @@ final class InlineSuggestionSession {
|
|||||||
@BinderThread
|
@BinderThread
|
||||||
@Override
|
@Override
|
||||||
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
||||||
IInlineSuggestionsResponseCallback callback, AutofillId imeFieldId,
|
IInlineSuggestionsResponseCallback callback) {
|
||||||
boolean inputViewStarted) {
|
if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
|
||||||
if (sDebug) {
|
mImeStatusListener.onInputMethodStartInput(mAutofillId);
|
||||||
Log.d(TAG,
|
|
||||||
"onInlineSuggestionsRequest() received: " + request + ", inputViewStarted="
|
|
||||||
+ inputViewStarted + ", imeFieldId=" + imeFieldId);
|
|
||||||
}
|
|
||||||
if (inputViewStarted) {
|
|
||||||
mImeStatusListener.onInputMethodStartInputView(imeFieldId);
|
|
||||||
} else {
|
|
||||||
mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
|
|
||||||
}
|
|
||||||
if (request != null && callback != null) {
|
if (request != null && callback != null) {
|
||||||
completeIfNot(new ImeResponse(request, callback));
|
completeIfNot(new ImeResponse(request, callback));
|
||||||
} else {
|
} else {
|
||||||
@@ -309,25 +316,50 @@ final class InlineSuggestionSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@BinderThread
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodStartInputView(AutofillId imeFieldId) {
|
public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
|
||||||
if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received on " + imeFieldId);
|
if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
|
||||||
mImeStatusListener.onInputMethodStartInputView(imeFieldId);
|
mImeStatusListener.onInputMethodStartInput(imeFieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
|
||||||
|
if (sDebug) {
|
||||||
|
Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
|
||||||
|
}
|
||||||
|
// TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for
|
||||||
|
// IME to show.
|
||||||
}
|
}
|
||||||
|
|
||||||
@BinderThread
|
@BinderThread
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodFinishInputView(AutofillId imeFieldId) {
|
public void onInputMethodStartInputView() {
|
||||||
if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received on " + imeFieldId);
|
if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
|
||||||
mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
|
mImeStatusListener.onInputMethodStartInputView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInputView() {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
|
||||||
|
mImeStatusListener.onInputMethodFinishInputView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInput() throws RemoteException {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
|
||||||
|
mImeStatusListener.onInputMethodFinishInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface ImeStatusListener {
|
private interface ImeStatusListener {
|
||||||
void onInputMethodStartInputView(AutofillId imeFieldId);
|
void onInputMethodStartInput(AutofillId imeFieldId);
|
||||||
|
|
||||||
void onInputMethodFinishInputView(AutofillId imeFieldId);
|
void onInputMethodStartInputView();
|
||||||
|
|
||||||
|
void onInputMethodFinishInputView();
|
||||||
|
|
||||||
|
void onInputMethodFinishInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2042,8 +2042,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
||||||
IInlineSuggestionsResponseCallback callback, AutofillId imeFieldId,
|
IInlineSuggestionsResponseCallback callback)
|
||||||
boolean inputViewStarted)
|
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
if (!mImePackageName.equals(request.getHostPackageName())) {
|
if (!mImePackageName.equals(request.getHostPackageName())) {
|
||||||
throw new SecurityException(
|
throw new SecurityException(
|
||||||
@@ -2053,17 +2052,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
|||||||
}
|
}
|
||||||
request.setHostDisplayId(mImeDisplayId);
|
request.setHostDisplayId(mImeDisplayId);
|
||||||
mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
|
mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
|
||||||
mCallback.onInlineSuggestionsRequest(request, callback, imeFieldId, inputViewStarted);
|
mCallback.onInlineSuggestionsRequest(request, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodStartInputView(AutofillId imeFieldId) throws RemoteException {
|
public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
|
||||||
mCallback.onInputMethodStartInputView(imeFieldId);
|
mCallback.onInputMethodStartInput(imeFieldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputMethodFinishInputView(AutofillId imeFieldId) throws RemoteException {
|
public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
|
||||||
mCallback.onInputMethodFinishInputView(imeFieldId);
|
mCallback.onInputMethodShowInputRequested(requestResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodStartInputView() throws RemoteException {
|
||||||
|
mCallback.onInputMethodStartInputView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInputView() throws RemoteException {
|
||||||
|
mCallback.onInputMethodFinishInputView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInput() throws RemoteException {
|
||||||
|
mCallback.onInputMethodFinishInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user