diff options
| author | 2020-02-08 02:01:40 +0000 | |
|---|---|---|
| committer | 2020-02-08 02:01:40 +0000 | |
| commit | 79ca0c84dba172ef506af1125dcdc89dc3522684 (patch) | |
| tree | 06c17acf1f7ff4106cbcf9a74c4ee00f19766c6f | |
| parent | 84e645c3a8acafa201a4de337d168ea3821b9b66 (diff) | |
| parent | ca782713a4537b8d2bd353cb2078326c2f5cf6f4 (diff) | |
Merge "Show augmented autofill suggestion inline iff both augmented autofill service and ime support inline."
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java | 168 | ||||
| -rw-r--r-- | services/autofill/java/com/android/server/autofill/Session.java | 122 |
2 files changed, 195 insertions, 95 deletions
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java new file mode 100644 index 000000000000..1fc48d235add --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -0,0 +1,168 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.RemoteException; +import android.util.Log; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Maintains an inline suggestion autofill session. + * + * <p> This class is thread safe. + */ +final class InlineSuggestionSession { + + private static final String TAG = "InlineSuggestionSession"; + private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + + @GuardedBy("mLock") + @Nullable + private CompletableFuture<ImeResponse> mPendingImeResponse; + + InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, + int userId, ComponentName componentName) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mLock = new Object(); + } + + public void createRequest(@NonNull AutofillId currentViewId) { + synchronized (mLock) { + cancelCurrentRequest(); + mPendingImeResponse = new CompletableFuture<>(); + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mUserId, mComponentName, currentViewId, + new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse)); + } + } + + @Nullable + public ImeResponse waitAndGetImeResponse() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse == null || pendingImeResponse.isCancelled()) { + return null; + } + try { + return pendingImeResponse.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return null; + } + + private void cancelCurrentRequest() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse != null) { + pendingImeResponse.cancel(true); + } + } + + @Nullable + @GuardedBy("mLock") + private CompletableFuture<ImeResponse> getPendingImeResponse() { + synchronized (mLock) { + return mPendingImeResponse; + } + } + + private static final class InlineSuggestionsRequestCallbackImpl + extends IInlineSuggestionsRequestCallback.Stub { + + private final CompletableFuture<ImeResponse> mResponse; + + private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response) { + mResponse = response; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsUnsupported() called."); + } + mResponse.cancel(true); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); + } + if (request != null && callback != null) { + mResponse.complete(new ImeResponse(request, callback)); + } else { + mResponse.cancel(true); + } + } + } + + /** + * A data class wrapping IME responses for the inline suggestion request. + */ + public static class ImeResponse { + @NonNull + private final InlineSuggestionsRequest mRequest; + + @NonNull + private final IInlineSuggestionsResponseCallback mCallback; + + ImeResponse(@NonNull InlineSuggestionsRequest request, + @NonNull IInlineSuggestionsResponseCallback callback) { + mRequest = request; + mCallback = callback; + } + + public InlineSuggestionsRequest getRequest() { + return mRequest; + } + + public IInlineSuggestionsResponseCallback getCallback() { + return mCallback; + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a25d7353edcb..53f85ea7b119 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -100,7 +100,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; -import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.InlineSuggestionFactory; @@ -114,11 +113,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -159,9 +153,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; - /** user id the session is for */ - public final int userId; - /** ID of the task associated with this session's activity */ public final int taskId; @@ -310,12 +301,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private boolean mForAugmentedAutofillOnly; - @NonNull - private final InputMethodManagerInternal mInputMethodManagerInternal; - @Nullable - @GuardedBy("mLock") - private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; + private final InlineSuggestionSession mInlineSuggestionSession; /** * Receiver of assist data from the app's {@link Activity}. @@ -416,12 +403,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - final InlineSuggestionsRequest suggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); request = new FillRequest(requestId, contexts, mClientState, flags, - suggestionsRequest); + imeResponse != null ? imeResponse.getRequest() : null); } if (mActivityToken != null) { @@ -619,75 +605,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabled()) { - mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, - mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); + mInlineSuggestionSession.createRequest(mCurrentViewId); } requestNewFillResponseLocked(viewState, newState, flags); } - private static final class InlineSuggestionsRequestCallbackImpl - extends IInlineSuggestionsRequestCallback.Stub { - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; - - private final CompletableFuture<InlineSuggestionsRequest> mRequest; - private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback; - - private InlineSuggestionsRequestCallbackImpl() { - mRequest = new CompletableFuture<>(); - mResponseCallback = new CompletableFuture<>(); - } - - @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { - if (sDebug) { - Log.d(TAG, "inline suggestions request unsupported, " - + "falling back to regular autofill"); - } - mRequest.cancel(true); - mResponseCallback.cancel(true); - } - - @Override - public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, - IInlineSuggestionsResponseCallback callback) throws RemoteException { - if (sDebug) { - Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); - } - mRequest.complete(request); - mResponseCallback.complete(callback); - } - - @Nullable - private InlineSuggestionsRequest getRequest() { - try { - return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - - @Nullable - private IInlineSuggestionsResponseCallback getResponseCallback() { - try { - return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - } - /** * Reads a new structure and then request a new fill response from the fill service. */ @@ -767,7 +690,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; - this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; @@ -786,7 +708,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); - mInputMethodManagerInternal = inputMethodManagerInternal; + mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, + componentName); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -2679,7 +2602,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (response.supportsInlineSuggestions()) { synchronized (mLock) { - if (requestShowInlineSuggestions(response, mInlineSuggestionsRequestCallback)) { + if (requestShowInlineSuggestionsLocked(response)) { //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, // rather than here where framework sends back the response. mService.logDatasetShown(id, mClientState); @@ -2722,21 +2645,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Returns whether we made a request to show inline suggestions. */ - private boolean requestShowInlineSuggestions(@NonNull FillResponse response, - @Nullable InlineSuggestionsRequestCallbackImpl callback) { + private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response) { final List<Dataset> datasets = response.getDatasets(); if (datasets == null) { Log.w(TAG, "response returned null datasets"); return false; } - if (callback == null || callback.getRequest() == null - || callback.getResponseCallback() == null) { + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); + if (imeResponse == null) { Log.w(TAG, "Session input method callback is not set yet"); return false; } - final InlineSuggestionsRequest request = callback.getRequest(); + final InlineSuggestionsRequest request = imeResponse.getRequest(); InlineSuggestionsResponse inlineSuggestionsResponse = InlineSuggestionFactory.createInlineSuggestionsResponse(request, response.getRequestId(), @@ -2747,7 +2670,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } }); try { - callback.getResponseCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); + imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); } catch (RemoteException e) { Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()"); return false; @@ -3029,12 +2952,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); + // There are 3 cases when augmented autofill should ask IME for a new request: + // 1. standard autofill provider is None + // 2. standard autofill provider doesn't support inline (and returns null response) + // 3. standard autofill provider supports inline, but isn't called because the field + // doesn't want autofill + if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabled()) { + if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); + mInlineSuggestionSession.createRequest(mCurrentViewId); + } + InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); final InlineSuggestionsRequest inlineSuggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + imeResponse != null ? imeResponse.getRequest() : null; final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; + imeResponse != null ? imeResponse.getCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> { synchronized (mLock) { |