Merge "Split autofill InlineSuggestionSession to two classes" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9822bd624c
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.autofill;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.autofill.AutofillId;
|
||||||
|
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||||
|
import android.view.inputmethod.InlineSuggestionsResponse;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.server.inputmethod.InputMethodManagerInternal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls the interaction with the IME for the inline suggestion sessions.
|
||||||
|
*/
|
||||||
|
final class AutofillInlineSessionController {
|
||||||
|
@NonNull
|
||||||
|
private final InputMethodManagerInternal mInputMethodManagerInternal;
|
||||||
|
private final int mUserId;
|
||||||
|
@NonNull
|
||||||
|
private final ComponentName mComponentName;
|
||||||
|
@NonNull
|
||||||
|
private final Object mLock;
|
||||||
|
@NonNull
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private AutofillInlineSuggestionsRequestSession mSession;
|
||||||
|
|
||||||
|
AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
|
||||||
|
int userId, ComponentName componentName, Handler handler, Object lock) {
|
||||||
|
mInputMethodManagerInternal = inputMethodManagerInternal;
|
||||||
|
mUserId = userId;
|
||||||
|
mComponentName = componentName;
|
||||||
|
mHandler = handler;
|
||||||
|
mLock = lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}.
|
||||||
|
*
|
||||||
|
* @param autofillId the Id of the field for which the request is for.
|
||||||
|
* @param requestConsumer the callback which will be invoked when IME responded or if it times
|
||||||
|
* out waiting for IME response.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId,
|
||||||
|
@NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
|
||||||
|
// TODO(b/151123764): rename the method to better reflect what it does.
|
||||||
|
if (mSession != null) {
|
||||||
|
// Send an empty response to IME and destroy the existing session.
|
||||||
|
mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(),
|
||||||
|
new InlineSuggestionsResponse(Collections.EMPTY_LIST));
|
||||||
|
mSession.destroySessionLocked();
|
||||||
|
}
|
||||||
|
// TODO(b/151123764): consider reusing the same AutofillInlineSession object for the
|
||||||
|
// same field.
|
||||||
|
mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId,
|
||||||
|
mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras);
|
||||||
|
mSession.onCreateInlineSuggestionsRequestLocked();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link InlineSuggestionsRequest} provided by IME for the last request.
|
||||||
|
*
|
||||||
|
* <p> The caller is responsible for making sure Autofill hears back from IME before calling
|
||||||
|
* this method, using the {@code requestConsumer} provided when calling {@link
|
||||||
|
* #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
|
||||||
|
if (mSession != null) {
|
||||||
|
return mSession.getInlineSuggestionsRequestLocked();
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
|
||||||
|
* to the IME.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
|
||||||
|
if (mSession != null) {
|
||||||
|
return mSession.onInlineSuggestionsResponseLocked(autofillId,
|
||||||
|
new InlineSuggestionsResponse(Collections.EMPTY_LIST));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
|
||||||
|
* on the {@code autofillId}.
|
||||||
|
*
|
||||||
|
* @return false if there is no session, or if the IME callback is not available in the session.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
|
||||||
|
@NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
|
||||||
|
// TODO(b/151123764): rename the method to better reflect what it does.
|
||||||
|
if (mSession != null) {
|
||||||
|
return mSession.onInlineSuggestionsResponseLocked(autofillId,
|
||||||
|
inlineSuggestionsResponse);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.autofill;
|
||||||
|
|
||||||
|
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||||
|
import static com.android.server.autofill.Helper.sDebug;
|
||||||
|
|
||||||
|
import android.annotation.BinderThread;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.view.autofill.AutofillId;
|
||||||
|
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||||
|
import android.view.inputmethod.InlineSuggestionsResponse;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
||||||
|
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
||||||
|
import com.android.internal.view.InlineSuggestionsRequestInfo;
|
||||||
|
import com.android.server.inputmethod.InputMethodManagerInternal;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains an inline suggestion session with the IME.
|
||||||
|
*
|
||||||
|
* <p> Each session corresponds to one request from the Autofill manager service to create an
|
||||||
|
* {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and
|
||||||
|
* sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME.
|
||||||
|
*/
|
||||||
|
final class AutofillInlineSuggestionsRequestSession {
|
||||||
|
|
||||||
|
private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName();
|
||||||
|
private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final InputMethodManagerInternal mInputMethodManagerInternal;
|
||||||
|
private final int mUserId;
|
||||||
|
@NonNull
|
||||||
|
private final ComponentName mComponentName;
|
||||||
|
@NonNull
|
||||||
|
private final Object mLock;
|
||||||
|
@NonNull
|
||||||
|
private final Handler mHandler;
|
||||||
|
@NonNull
|
||||||
|
private final Bundle mUiExtras;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@NonNull
|
||||||
|
private AutofillId mAutofillId;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private Consumer<InlineSuggestionsRequest> mImeRequestConsumer;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mImeRequestReceived;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private InlineSuggestionsRequest mImeRequest;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private IInlineSuggestionsResponseCallback mResponseCallback;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private Runnable mTimeoutCallback;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private AutofillId mImeCurrentFieldId;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mImeInputStarted;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mImeInputViewStarted;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@Nullable
|
||||||
|
private InlineSuggestionsResponse mInlineSuggestionsResponse;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mPreviousResponseIsNotEmpty;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mDestroyed = false;
|
||||||
|
|
||||||
|
AutofillInlineSuggestionsRequestSession(
|
||||||
|
@NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId,
|
||||||
|
@NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock,
|
||||||
|
@NonNull AutofillId autofillId,
|
||||||
|
@NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
|
||||||
|
mInputMethodManagerInternal = inputMethodManagerInternal;
|
||||||
|
mUserId = userId;
|
||||||
|
mComponentName = componentName;
|
||||||
|
mHandler = handler;
|
||||||
|
mLock = lock;
|
||||||
|
mUiExtras = uiExtras;
|
||||||
|
|
||||||
|
mAutofillId = autofillId;
|
||||||
|
mImeRequestConsumer = requestConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
@NonNull
|
||||||
|
AutofillId getAutofillIdLocked() {
|
||||||
|
return mAutofillId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link InlineSuggestionsRequest} provided by IME.
|
||||||
|
*
|
||||||
|
* <p> The caller is responsible for making sure Autofill hears back from IME before calling
|
||||||
|
* this method, using the {@link #mImeRequestConsumer}.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
|
||||||
|
if (mDestroyed) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(mImeRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
|
||||||
|
* on the {@code autofillId}.
|
||||||
|
*
|
||||||
|
* @return false if the IME callback is not available.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
|
||||||
|
@NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
|
||||||
|
if (mDestroyed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
|
||||||
|
if (mImeRequest == null || mResponseCallback == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO(b/151123764): each session should only correspond to one field.
|
||||||
|
mAutofillId = autofillId;
|
||||||
|
mInlineSuggestionsResponse = inlineSuggestionsResponse;
|
||||||
|
maybeUpdateResponseToImeLocked();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method must be called when the session is destroyed, to avoid further callbacks from/to
|
||||||
|
* the IME.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
void destroySessionLocked() {
|
||||||
|
mDestroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the IME to create an {@link InlineSuggestionsRequest}.
|
||||||
|
*
|
||||||
|
* <p> This method should only be called once per session.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
void onCreateInlineSuggestionsRequestLocked() {
|
||||||
|
if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
|
||||||
|
if (mDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
|
||||||
|
new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
|
||||||
|
new InlineSuggestionsRequestCallbackImpl(this));
|
||||||
|
mTimeoutCallback = () -> {
|
||||||
|
Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
|
||||||
|
handleOnReceiveImeRequest(null, null);
|
||||||
|
};
|
||||||
|
mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally sends inline response to the IME, depending on the current state.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void maybeUpdateResponseToImeLocked() {
|
||||||
|
if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called");
|
||||||
|
if (mDestroyed || mResponseCallback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
|
||||||
|
// 1. if previous response is not empty, and IME just become invisible, then send
|
||||||
|
// empty response to make sure existing responses don't stick around on the IME.
|
||||||
|
// Although the inline suggestions should disappear when IME hides which removes them
|
||||||
|
// from the view hierarchy, but we still send an empty response to be extra safe.
|
||||||
|
|
||||||
|
// TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is
|
||||||
|
// fixed.
|
||||||
|
//if (sDebug) Log.d(TAG, "Send empty inline response");
|
||||||
|
//updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections
|
||||||
|
// .EMPTY_LIST));
|
||||||
|
//mPreviousResponseIsNotEmpty = false;
|
||||||
|
} else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
|
||||||
|
mImeCurrentFieldId)) {
|
||||||
|
// 2. if IME is visible, and response is not null, send the response
|
||||||
|
boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
|
||||||
|
if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
|
||||||
|
// No-op if both the previous response and current response are empty.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sDebug) {
|
||||||
|
Log.d(TAG, "Send inline response: "
|
||||||
|
+ mInlineSuggestionsResponse.getInlineSuggestions().size());
|
||||||
|
}
|
||||||
|
updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
|
||||||
|
// TODO(b/149945531): don't set the response to null so it's cached, once the bug is
|
||||||
|
// fixed.
|
||||||
|
mInlineSuggestionsResponse = null;
|
||||||
|
mPreviousResponseIsNotEmpty = !isEmptyResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the {@code response} to the IME, assuming all the relevant checks are already done.
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) {
|
||||||
|
if (mDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the {@code request} and {@code callback} received from the IME.
|
||||||
|
*
|
||||||
|
* <p> Should only invoked in the {@link #mHandler} thread.
|
||||||
|
*/
|
||||||
|
private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request,
|
||||||
|
@Nullable IInlineSuggestionsResponseCallback callback) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mDestroyed || mImeRequestReceived) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mImeRequestReceived = true;
|
||||||
|
|
||||||
|
if (mTimeoutCallback != null) {
|
||||||
|
if (sDebug) Log.d(TAG, "removing timeout callback");
|
||||||
|
mHandler.removeCallbacks(mTimeoutCallback);
|
||||||
|
mTimeoutCallback = null;
|
||||||
|
}
|
||||||
|
if (request != null && callback != null) {
|
||||||
|
mImeRequest = request;
|
||||||
|
mResponseCallback = callback;
|
||||||
|
handleOnReceiveImeStatusUpdated(mAutofillId, true, false);
|
||||||
|
}
|
||||||
|
if (mImeRequestConsumer != null) {
|
||||||
|
// Note that mImeRequest is only set if both request and callback are non-null.
|
||||||
|
mImeRequestConsumer.accept(mImeRequest);
|
||||||
|
mImeRequestConsumer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the IME status updates received from the IME.
|
||||||
|
*
|
||||||
|
* <p> Should only be invoked in the {@link #mHandler} thread.
|
||||||
|
*/
|
||||||
|
private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted,
|
||||||
|
boolean imeInputViewStarted) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mImeCurrentFieldId != null) {
|
||||||
|
boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted);
|
||||||
|
boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted);
|
||||||
|
mImeInputStarted = imeInputStarted;
|
||||||
|
mImeInputViewStarted = imeInputViewStarted;
|
||||||
|
if (imeInputStartedChanged || imeInputViewStartedChanged) {
|
||||||
|
maybeUpdateResponseToImeLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the IME status updates received from the IME.
|
||||||
|
*
|
||||||
|
* <p> Should only be invoked in the {@link #mHandler} thread.
|
||||||
|
*/
|
||||||
|
private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId,
|
||||||
|
boolean imeInputStarted, boolean imeInputViewStarted) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (imeFieldId != null) {
|
||||||
|
mImeCurrentFieldId = imeFieldId;
|
||||||
|
}
|
||||||
|
handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class InlineSuggestionsRequestCallbackImpl extends
|
||||||
|
IInlineSuggestionsRequestCallback.Stub {
|
||||||
|
|
||||||
|
private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
|
||||||
|
|
||||||
|
private InlineSuggestionsRequestCallbackImpl(
|
||||||
|
AutofillInlineSuggestionsRequestSession session) {
|
||||||
|
mSession = new WeakReference<>(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
@Override
|
||||||
|
public void onInlineSuggestionsUnsupported() throws RemoteException {
|
||||||
|
if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
|
||||||
|
null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
@Override
|
||||||
|
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
||||||
|
IInlineSuggestionsResponseCallback callback) {
|
||||||
|
if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
|
||||||
|
request, callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
|
||||||
|
session, imeFieldId, true, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
|
||||||
|
if (sDebug) {
|
||||||
|
Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
@Override
|
||||||
|
public void onInputMethodStartInputView() {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
|
||||||
|
session, true, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInputView() {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
|
||||||
|
session, true, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputMethodFinishInput() throws RemoteException {
|
||||||
|
if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
|
||||||
|
final AutofillInlineSuggestionsRequestSession session = mSession.get();
|
||||||
|
if (session != null) {
|
||||||
|
session.mHandler.sendMessage(obtainMessage(
|
||||||
|
AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
|
||||||
|
session, false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.android.server.autofill;
|
|
||||||
|
|
||||||
import static com.android.server.autofill.Helper.sDebug;
|
|
||||||
|
|
||||||
import android.annotation.BinderThread;
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Slog;
|
|
||||||
import android.view.autofill.AutofillId;
|
|
||||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
|
||||||
import android.view.inputmethod.InlineSuggestionsResponse;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
|
||||||
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
|
||||||
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
|
||||||
import com.android.internal.view.InlineSuggestionsRequestInfo;
|
|
||||||
import com.android.server.inputmethod.InputMethodManagerInternal;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maintains an autofill inline suggestion session that communicates with the IME.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* The same session may be reused for multiple input fields involved in the same autofill
|
|
||||||
* {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one
|
|
||||||
* {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline
|
|
||||||
* suggestions for different input fields.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This class is the sole place in Autofill responsible for directly communicating with the IME. It
|
|
||||||
* receives the IME input view start/finish events, with the associated IME field Id. It uses the
|
|
||||||
* information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
|
|
||||||
* some of the response will be cached locally and only be sent when the IME is ready to show them.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
|
|
||||||
* side flow.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This class should hold the same lock as {@link Session} as they call into each other.
|
|
||||||
*/
|
|
||||||
final class InlineSuggestionSession {
|
|
||||||
|
|
||||||
private static final String TAG = "AfInlineSuggestionSession";
|
|
||||||
private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final InputMethodManagerInternal mInputMethodManagerInternal;
|
|
||||||
private final int mUserId;
|
|
||||||
@NonNull
|
|
||||||
private final ComponentName mComponentName;
|
|
||||||
@NonNull
|
|
||||||
private final Object mLock;
|
|
||||||
@NonNull
|
|
||||||
private final ImeStatusListener mImeStatusListener;
|
|
||||||
@NonNull
|
|
||||||
private final Handler mHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To avoid the race condition, one should not access {@code mPendingImeResponse} without
|
|
||||||
* holding the {@code mLock}. For consuming the existing value, tt's recommended to use
|
|
||||||
* {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
|
|
||||||
*/
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
@Nullable
|
|
||||||
private CompletableFuture<ImeResponse> mPendingImeResponse;
|
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
@Nullable
|
|
||||||
private AutofillResponse mPendingAutofillResponse;
|
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private boolean mIsLastResponseNonEmpty = false;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private AutofillId mImeFieldId = null;
|
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private boolean mImeInputViewStarted = false;
|
|
||||||
|
|
||||||
InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
|
|
||||||
int userId, ComponentName componentName, Handler handler, Object lock) {
|
|
||||||
mInputMethodManagerInternal = inputMethodManagerInternal;
|
|
||||||
mUserId = userId;
|
|
||||||
mComponentName = componentName;
|
|
||||||
mHandler = handler;
|
|
||||||
mLock = lock;
|
|
||||||
mImeStatusListener = new ImeStatusListener() {
|
|
||||||
@Override
|
|
||||||
public void onInputMethodStartInput(AutofillId imeFieldId) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mImeFieldId = imeFieldId;
|
|
||||||
mImeInputViewStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputMethodStartInputView() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mImeInputViewStarted = true;
|
|
||||||
AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
|
|
||||||
if (pendingAutofillResponse != null
|
|
||||||
&& pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
|
|
||||||
mImeFieldId)) {
|
|
||||||
mPendingAutofillResponse = null;
|
|
||||||
onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
|
|
||||||
pendingAutofillResponse.mResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputMethodFinishInputView() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mImeInputViewStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputMethodFinishInput() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mImeFieldId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId,
|
|
||||||
@NonNull Consumer<InlineSuggestionsRequest> requestConsumer) {
|
|
||||||
if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
|
|
||||||
|
|
||||||
synchronized (mLock) {
|
|
||||||
// Clean up all the state about the previous request.
|
|
||||||
hideInlineSuggestionsUi(autofillId);
|
|
||||||
mImeFieldId = null;
|
|
||||||
mImeInputViewStarted = false;
|
|
||||||
if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
|
|
||||||
mPendingImeResponse.complete(null);
|
|
||||||
}
|
|
||||||
mPendingImeResponse = new CompletableFuture<>();
|
|
||||||
// TODO(b/146454892): pipe the uiExtras from the ExtServices.
|
|
||||||
mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
|
|
||||||
mUserId,
|
|
||||||
new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
|
|
||||||
new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
|
|
||||||
mImeStatusListener, requestConsumer, mHandler, mLock));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() {
|
|
||||||
final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
|
|
||||||
if (pendingImeResponse == null || !pendingImeResponse.isDone()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
if (mIsLastResponseNonEmpty) {
|
|
||||||
return onInlineSuggestionsResponseLocked(autofillId,
|
|
||||||
new InlineSuggestionsResponse(Collections.EMPTY_LIST));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId,
|
|
||||||
@NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
|
|
||||||
@NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
|
|
||||||
final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
|
|
||||||
if (completedImsResponse == null || !completedImsResponse.isDone()) {
|
|
||||||
if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// There is no need to wait on the CompletableFuture since it should have been completed.
|
|
||||||
ImeResponse imeResponse = completedImsResponse.getNow(null);
|
|
||||||
if (imeResponse == null) {
|
|
||||||
if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
|
|
||||||
// only compare the view id for now.
|
|
||||||
if (!mImeInputViewStarted || mImeFieldId == null
|
|
||||||
|| autofillId.getViewId() != mImeFieldId.getViewId()) {
|
|
||||||
if (sDebug) {
|
|
||||||
Log.d(TAG,
|
|
||||||
"onInlineSuggestionsResponseLocked not sent because input view is not "
|
|
||||||
+ "started for " + autofillId);
|
|
||||||
}
|
|
||||||
mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
|
|
||||||
// TODO(b/149442582): Although we are not sending the response to IME right away, we
|
|
||||||
// still return true to indicate that the response may be sent eventually, such that
|
|
||||||
// the dropdown UI will not be shown. This may not be the desired behavior in the
|
|
||||||
// auto-focus case where IME isn't shown after switching back to an activity. We may
|
|
||||||
// revisit this.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
|
|
||||||
inlineSuggestionsResponse);
|
|
||||||
mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty();
|
|
||||||
if (sDebug) {
|
|
||||||
Log.d(TAG, "Autofill sends inline response to IME: "
|
|
||||||
+ inlineSuggestionsResponse.getInlineSuggestions().size());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private CompletableFuture<ImeResponse> getPendingImeResponse() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
return mPendingImeResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class InlineSuggestionsRequestCallbackImpl
|
|
||||||
extends IInlineSuggestionsRequestCallback.Stub {
|
|
||||||
|
|
||||||
private final Object mLock;
|
|
||||||
private final AutofillId mAutofillId;
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private final CompletableFuture<ImeResponse> mResponse;
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private final Consumer<InlineSuggestionsRequest> mRequestConsumer;
|
|
||||||
private final ImeStatusListener mImeStatusListener;
|
|
||||||
private final Handler mHandler;
|
|
||||||
private final Runnable mTimeoutCallback;
|
|
||||||
|
|
||||||
private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
|
|
||||||
CompletableFuture<ImeResponse> response,
|
|
||||||
ImeStatusListener imeStatusListener,
|
|
||||||
Consumer<InlineSuggestionsRequest> requestConsumer,
|
|
||||||
Handler handler, Object lock) {
|
|
||||||
mAutofillId = autofillId;
|
|
||||||
mResponse = response;
|
|
||||||
mImeStatusListener = imeStatusListener;
|
|
||||||
mRequestConsumer = requestConsumer;
|
|
||||||
mLock = lock;
|
|
||||||
|
|
||||||
mHandler = handler;
|
|
||||||
mTimeoutCallback = () -> {
|
|
||||||
Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
|
|
||||||
completeIfNot(null);
|
|
||||||
};
|
|
||||||
mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void completeIfNot(@Nullable ImeResponse response) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
if (mResponse.isDone()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mResponse.complete(response);
|
|
||||||
mRequestConsumer.accept(response == null ? null : response.mRequest);
|
|
||||||
mHandler.removeCallbacks(mTimeoutCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BinderThread
|
|
||||||
@Override
|
|
||||||
public void onInlineSuggestionsUnsupported() throws RemoteException {
|
|
||||||
if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
|
|
||||||
completeIfNot(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BinderThread
|
|
||||||
@Override
|
|
||||||
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
|
|
||||||
IInlineSuggestionsResponseCallback callback) {
|
|
||||||
if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
|
|
||||||
mImeStatusListener.onInputMethodStartInput(mAutofillId);
|
|
||||||
if (request != null && callback != null) {
|
|
||||||
completeIfNot(new ImeResponse(request, callback));
|
|
||||||
} else {
|
|
||||||
completeIfNot(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
|
|
||||||
if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + 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
|
|
||||||
@Override
|
|
||||||
public void onInputMethodStartInputView() {
|
|
||||||
if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
|
|
||||||
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 {
|
|
||||||
void onInputMethodStartInput(AutofillId imeFieldId);
|
|
||||||
|
|
||||||
void onInputMethodStartInputView();
|
|
||||||
|
|
||||||
void onInputMethodFinishInputView();
|
|
||||||
|
|
||||||
void onInputMethodFinishInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A data class wrapping Autofill responses for the inline suggestion request.
|
|
||||||
*/
|
|
||||||
private static class AutofillResponse {
|
|
||||||
@NonNull
|
|
||||||
final AutofillId mAutofillId;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
final InlineSuggestionsResponse mResponse;
|
|
||||||
|
|
||||||
AutofillResponse(@NonNull AutofillId autofillId,
|
|
||||||
@NonNull InlineSuggestionsResponse response) {
|
|
||||||
mAutofillId = autofillId;
|
|
||||||
mResponse = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A data class wrapping IME responses for the create inline suggestions request.
|
|
||||||
*/
|
|
||||||
private static class ImeResponse {
|
|
||||||
@NonNull
|
|
||||||
final InlineSuggestionsRequest mRequest;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
final IInlineSuggestionsResponseCallback mCallback;
|
|
||||||
|
|
||||||
ImeResponse(@NonNull InlineSuggestionsRequest request,
|
|
||||||
@NonNull IInlineSuggestionsResponseCallback callback) {
|
|
||||||
mRequest = request;
|
|
||||||
mCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
InlineSuggestionsRequest getRequest() {
|
|
||||||
return mRequest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -304,7 +304,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
private boolean mForAugmentedAutofillOnly;
|
private boolean mForAugmentedAutofillOnly;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final InlineSuggestionSession mInlineSuggestionSession;
|
private final AutofillInlineSessionController mInlineSessionController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receiver of assist data from the app's {@link Activity}.
|
* Receiver of assist data from the app's {@link Activity}.
|
||||||
@@ -720,8 +720,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
|
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
|
||||||
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
|
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
|
||||||
if (inlineSuggestionsRequestConsumer != null) {
|
if (inlineSuggestionsRequestConsumer != null) {
|
||||||
mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
|
// TODO(b/146454892): pipe the uiExtras from the ExtServices.
|
||||||
inlineSuggestionsRequestConsumer);
|
mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
|
||||||
|
inlineSuggestionsRequestConsumer, Bundle.EMPTY);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
|
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
|
||||||
@@ -777,8 +778,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
|
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
|
||||||
setClientLocked(client);
|
setClientLocked(client);
|
||||||
|
|
||||||
mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
|
mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
|
||||||
componentName, handler, mLock);
|
userId, componentName, handler, mLock);
|
||||||
|
|
||||||
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
|
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
|
||||||
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
|
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
|
||||||
@@ -2561,7 +2562,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
|
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
|
||||||
mUi.hideFillUi(this);
|
mUi.hideFillUi(this);
|
||||||
hideAugmentedAutofillLocked(viewState);
|
hideAugmentedAutofillLocked(viewState);
|
||||||
mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
|
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
|
||||||
mCurrentViewId = null;
|
mCurrentViewId = null;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -2779,7 +2780,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
|
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
|
||||||
@Nullable String filterText) {
|
@Nullable String filterText) {
|
||||||
final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
|
final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
|
||||||
mInlineSuggestionSession.getInlineSuggestionsRequest();
|
mInlineSessionController.getInlineSuggestionsRequestLocked();
|
||||||
if (!inlineSuggestionsRequest.isPresent()) {
|
if (!inlineSuggestionsRequest.isPresent()) {
|
||||||
Log.w(TAG, "InlineSuggestionsRequest unavailable");
|
Log.w(TAG, "InlineSuggestionsRequest unavailable");
|
||||||
return false;
|
return false;
|
||||||
@@ -2801,7 +2802,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
|
inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
|
||||||
this, () -> {
|
this, () -> {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
|
mInlineSessionController.hideInlineSuggestionsUiLocked(
|
||||||
|
mCurrentViewId);
|
||||||
}
|
}
|
||||||
}, remoteRenderService);
|
}, remoteRenderService);
|
||||||
if (inlineSuggestionsResponse == null) {
|
if (inlineSuggestionsResponse == null) {
|
||||||
@@ -2809,7 +2811,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
|
return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
|
||||||
inlineSuggestionsResponse);
|
inlineSuggestionsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3106,8 +3108,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
focusedId,
|
focusedId,
|
||||||
currentValue, inlineSuggestionsRequest,
|
currentValue, inlineSuggestionsRequest,
|
||||||
/*inlineSuggestionsCallback=*/
|
/*inlineSuggestionsCallback=*/
|
||||||
response -> mInlineSuggestionSession.onInlineSuggestionsResponse(
|
response -> {
|
||||||
mCurrentViewId, response),
|
synchronized (mLock) {
|
||||||
|
return mInlineSessionController
|
||||||
|
.onInlineSuggestionsResponseLocked(
|
||||||
|
mCurrentViewId, response);
|
||||||
|
}
|
||||||
|
},
|
||||||
/*onErrorCallback=*/ () -> {
|
/*onErrorCallback=*/ () -> {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
cancelAugmentedAutofillLocked();
|
cancelAugmentedAutofillLocked();
|
||||||
@@ -3125,11 +3132,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
|||||||
&& (mForAugmentedAutofillOnly
|
&& (mForAugmentedAutofillOnly
|
||||||
|| !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
|
|| !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
|
||||||
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
|
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
|
||||||
mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
|
// TODO(b/146454892): pipe the uiExtras from the ExtServices.
|
||||||
/*requestConsumer=*/ requestAugmentedAutofill);
|
mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
|
||||||
|
/*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY);
|
||||||
} else {
|
} else {
|
||||||
requestAugmentedAutofill.accept(
|
requestAugmentedAutofill.accept(
|
||||||
mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null));
|
mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
|
||||||
}
|
}
|
||||||
if (mAugmentedAutofillDestroyer == null) {
|
if (mAugmentedAutofillDestroyer == null) {
|
||||||
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
|
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
|
||||||
|
|||||||
Reference in New Issue
Block a user